mirror of https://github.com/rancher/fleet.git
687 lines
26 KiB
Go
687 lines
26 KiB
Go
package git_test
|
|
|
|
import (
|
|
"encoding/pem"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
httpgit "github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
gossh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"golang.org/x/crypto/ssh"
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
fleetgithub "github.com/rancher/fleet/internal/github"
|
|
"github.com/rancher/fleet/pkg/git"
|
|
)
|
|
|
|
type fakeGetter struct {
|
|
auth *httpgit.BasicAuth
|
|
err error
|
|
}
|
|
|
|
func (f fakeGetter) Get(appID, instID int64, pem []byte) (*httpgit.BasicAuth, error) {
|
|
return f.auth, f.err
|
|
}
|
|
|
|
const gitClientTimeout = time.Second * 30
|
|
|
|
var _ = Describe("git's GetAuthFromSecret tests", func() {
|
|
var (
|
|
secret *corev1.Secret
|
|
)
|
|
|
|
Context("Nil secret", func() {
|
|
It("returns no error and no auth when known hosts are empty", func() {
|
|
auth, err := git.GetAuthFromSecret("ssh://foo.bar", nil, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
|
|
auth, err = git.GetAuthFromSecret("http://foo.bar", nil, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("Basic auth with no username nor password", func() {
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeBasicAuth,
|
|
}
|
|
})
|
|
It("returns no error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("Secret is not basic-auth nor ssh, has no github app keys", func() {
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeTLS,
|
|
}
|
|
})
|
|
|
|
It("returns no error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("Secret is not basic-auth nor ssh, has some github app keys", func() {
|
|
var (
|
|
gitHubAppID = []byte("123")
|
|
githubAppInstallationID = []byte("456")
|
|
)
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeTLS,
|
|
Data: map[string][]byte{
|
|
fleetgithub.GitHubAppAuthIDKey: gitHubAppID,
|
|
fleetgithub.GitHubAppAuthInstallationIDKey: githubAppInstallationID,
|
|
},
|
|
}
|
|
})
|
|
|
|
It("returns no error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("Github app auth with all expected keys", func() {
|
|
var (
|
|
gitHubAppID = []byte("123")
|
|
githubAppInstallationID = []byte("456")
|
|
githubAppPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEogIBAAKCAQEAmVh/5bCTwmFU+F7OWyYT6JFkG8V06AdesKSMyeJwT4kGs3Pm
|
|
vKEzKd/CExhd25Tzk5CD8jj6x9usZOtnI0rmCJEgkviWbk6b0K0jPs2b4a6fSbvE
|
|
GpSYheS89cQ7m8YrQr6MuqstjpS1Yz/uWwN0DCrNupyf0GkesqKLlgElPuwcfeQo
|
|
OmyLhY2xViK2ctLzricbKqDMqCFd1WdaYEut6lh3z/Gy/tPk/CJVkP1VGC+KTaer
|
|
vyRKnH9By1VsXUOzOT1NVFjOJfXIqtyTwnE+d24WR9mPcw7kPReiXyS6DoUfcmiq
|
|
4irqaN1smp52iX7EbWsIhir74TrBP51q1M+QFQIDAQABAoIBACTmMNB6bvPFLAcf
|
|
+RPh08SQx8APAZSbwWNMFTy3KkNZO62O5CTbvU4EM9UYde1SqFIH4lg08dOJvrAC
|
|
HS1W5oeFNItpGfmtHL1YDDUekLX7qQS7E/M5coI1imqxL47KXrqO05pPeoTmr8cU
|
|
KSzpZdFPs3WGHsatpN9jUadk2yuKkVEjWxMLOtnvbxuhPSuJmhJ5oajCHhGcGD1k
|
|
RMBsPrvRy9J5nEEfYA+KKpWDhIiS6fP5n0YvroIpcJVU5Fo/EZClNrEBkVl0bOms
|
|
d103PWADAputJrTYpv1b/YsT3STM698DhXsTtvK3RvXouJA+P+JVIO5wnH3ySDhL
|
|
L73FgcECgYEA3ee6oNybgMiL9dXAo4lD2BbKU/fbgXZXZLlxrex1x08u9EjOf2+h
|
|
R7ZvJnmwbTtpwis0mCbkvS6SrMO4qmlTzF0KvWWu6dobBpDUIVBU0w4No9J4c7BS
|
|
lYEGFzJH9VMugZMmstT0Yn9ugntShQjf+gQp6RSr4odssNFqqiOCB8kCgYEAsOgY
|
|
5wHEvR+7v+6iaO+/ZGFgBcmt+B3aC+pwjsyVVpUuAAA3Dyb8T5CIMi44Ys/1C3TC
|
|
b/cypuLFzgdccjqHdZ7z4Tmwo5skUmHTtEsb01mG2FxBBm4a81+FwoN+QRnZDpj6
|
|
JQIfWWmbsP56XPOaqlMYfIhLqBkNwtuvhY4fA+0CgYBwrKJh3cKD0NDoYcHwB9nQ
|
|
FjpkCn2Frg5QEa18T426RyWjWnin0onE/QhRNAb2X+2ibwfEnjMVMFm/qZ3RwauQ
|
|
IEo8wy3ehiWk3tMnmz+G7yLT5SHONGCqkxoBm0FYewUpPAuxUFpKzUPSs0XCUTBR
|
|
Jd4WAK4KVxNEcQFFJMR4qQKBgEc8TrrG1YgqfRneZ/vFftZW96mc+rbMnn7p2oVG
|
|
EGSbEbjiXUl2s2b+ljlOr1nqz4vbamhXrEfTTT+Xazx8IQvWA/KPnndjA49A4VTa
|
|
YcwLYudAztZeA/A4aM5Y0MA6PlNIeoHohuMkSZNOBcvkNEWdzGBpKb34yLfMarNm
|
|
9UpJAoGAU4sGLwlAHGo+PXUi0PyLtQcxcbytkLAQsKMpuZDvNT/KAmu0kJ0p9InN
|
|
5VKnu9SpmXPxjinS8Mg9QXLrfi5SArEllzfXrgW9OU7ht2xandDD+B8S1cmZF+Yz
|
|
1salKM9mBBkl0sWraqtzQSEDjPeAz8P4TpQKn6kIMiZkMnrurvI=
|
|
-----END RSA PRIVATE KEY-----`)
|
|
origGetter = git.GitHubAppGetter
|
|
)
|
|
BeforeEach(func() {
|
|
git.GitHubAppGetter = fakeGetter{
|
|
auth: &httpgit.BasicAuth{Username: "x-access-token", Password: "token"},
|
|
}
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
fleetgithub.GitHubAppAuthIDKey: gitHubAppID,
|
|
fleetgithub.GitHubAppAuthInstallationIDKey: githubAppInstallationID,
|
|
fleetgithub.GitHubAppAuthPrivateKeyKey: githubAppPrivateKey,
|
|
},
|
|
}
|
|
})
|
|
AfterEach(func() { git.GitHubAppGetter = origGetter })
|
|
It("returns the basic auth Auth and no error", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(Equal(&httpgit.BasicAuth{
|
|
Username: "x-access-token",
|
|
Password: "token",
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("Github app auth with all expected keys, invalid private key", func() {
|
|
var (
|
|
gitHubAppID = []byte("123")
|
|
githubAppInstallationID = []byte("456")
|
|
githubAppPrivateKey = []byte(`not a valid private key`)
|
|
)
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
fleetgithub.GitHubAppAuthIDKey: gitHubAppID,
|
|
fleetgithub.GitHubAppAuthInstallationIDKey: githubAppInstallationID,
|
|
fleetgithub.GitHubAppAuthPrivateKeyKey: githubAppPrivateKey,
|
|
},
|
|
}
|
|
})
|
|
It("returns an error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
Expect(err.Error()).To(ContainSubstring("pem decode failed for app"))
|
|
})
|
|
})
|
|
|
|
Context("Github app auth with all expected keys, non-numeric app id", func() {
|
|
var (
|
|
gitHubAppID = []byte("abc")
|
|
githubAppInstallationID = []byte("456")
|
|
githubAppPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEogIBAAKCAQEAmVh/5bCTwmFU+F7OWyYT6JFkG8V06AdesKSMyeJwT4kGs3Pm
|
|
vKEzKd/CExhd25Tzk5CD8jj6x9usZOtnI0rmCJEgkviWbk6b0K0jPs2b4a6fSbvE
|
|
GpSYheS89cQ7m8YrQr6MuqstjpS1Yz/uWwN0DCrNupyf0GkesqKLlgElPuwcfeQo
|
|
OmyLhY2xViK2ctLzricbKqDMqCFd1WdaYEut6lh3z/Gy/tPk/CJVkP1VGC+KTaer
|
|
vyRKnH9By1VsXUOzOT1NVFjOJfXIqtyTwnE+d24WR9mPcw7kPReiXyS6DoUfcmiq
|
|
4irqaN1smp52iX7EbWsIhir74TrBP51q1M+QFQIDAQABAoIBACTmMNB6bvPFLAcf
|
|
+RPh08SQx8APAZSbwWNMFTy3KkNZO62O5CTbvU4EM9UYde1SqFIH4lg08dOJvrAC
|
|
HS1W5oeFNItpGfmtHL1YDDUekLX7qQS7E/M5coI1imqxL47KXrqO05pPeoTmr8cU
|
|
KSzpZdFPs3WGHsatpN9jUadk2yuKkVEjWxMLOtnvbxuhPSuJmhJ5oajCHhGcGD1k
|
|
RMBsPrvRy9J5nEEfYA+KKpWDhIiS6fP5n0YvroIpcJVU5Fo/EZClNrEBkVl0bOms
|
|
d103PWADAputJrTYpv1b/YsT3STM698DhXsTtvK3RvXouJA+P+JVIO5wnH3ySDhL
|
|
L73FgcECgYEA3ee6oNybgMiL9dXAo4lD2BbKU/fbgXZXZLlxrex1x08u9EjOf2+h
|
|
R7ZvJnmwbTtpwis0mCbkvS6SrMO4qmlTzF0KvWWu6dobBpDUIVBU0w4No9J4c7BS
|
|
lYEGFzJH9VMugZMmstT0Yn9ugntShQjf+gQp6RSr4odssNFqqiOCB8kCgYEAsOgY
|
|
5wHEvR+7v+6iaO+/ZGFgBcmt+B3aC+pwjsyVVpUuAAA3Dyb8T5CIMi44Ys/1C3TC
|
|
b/cypuLFzgdccjqHdZ7z4Tmwo5skUmHTtEsb01mG2FxBBm4a81+FwoN+QRnZDpj6
|
|
JQIfWWmbsP56XPOaqlMYfIhLqBkNwtuvhY4fA+0CgYBwrKJh3cKD0NDoYcHwB9nQ
|
|
FjpkCn2Frg5QEa18T426RyWjWnin0onE/QhRNAb2X+2ibwfEnjMVMFm/qZ3RwauQ
|
|
IEo8wy3ehiWk3tMnmz+G7yLT5SHONGCqkxoBm0FYewUpPAuxUFpKzUPSs0XCUTBR
|
|
Jd4WAK4KVxNEcQFFJMR4qQKBgEc8TrrG1YgqfRneZ/vFftZW96mc+rbMnn7p2oVG
|
|
EGSbEbjiXUl2s2b+ljlOr1nqz4vbamhXrEfTTT+Xazx8IQvWA/KPnndjA49A4VTa
|
|
YcwLYudAztZeA/A4aM5Y0MA6PlNIeoHohuMkSZNOBcvkNEWdzGBpKb34yLfMarNm
|
|
9UpJAoGAU4sGLwlAHGo+PXUi0PyLtQcxcbytkLAQsKMpuZDvNT/KAmu0kJ0p9InN
|
|
5VKnu9SpmXPxjinS8Mg9QXLrfi5SArEllzfXrgW9OU7ht2xandDD+B8S1cmZF+Yz
|
|
1salKM9mBBkl0sWraqtzQSEDjPeAz8P4TpQKn6kIMiZkMnrurvI=
|
|
-----END RSA PRIVATE KEY-----`)
|
|
)
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeOpaque,
|
|
Data: map[string][]byte{
|
|
fleetgithub.GitHubAppAuthIDKey: gitHubAppID,
|
|
fleetgithub.GitHubAppAuthInstallationIDKey: githubAppInstallationID,
|
|
fleetgithub.GitHubAppAuthPrivateKeyKey: githubAppPrivateKey,
|
|
},
|
|
}
|
|
})
|
|
It("returns an error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
Expect(err.Error()).To(ContainSubstring("parsing \"abc\": invalid syntax"))
|
|
})
|
|
})
|
|
|
|
Context("Basic auth with username and password", func() {
|
|
var (
|
|
username = []byte("username-test")
|
|
password = []byte("1234issupersecure")
|
|
)
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeBasicAuth,
|
|
Data: map[string][]byte{
|
|
corev1.BasicAuthUsernameKey: username,
|
|
corev1.BasicAuthPasswordKey: password,
|
|
},
|
|
}
|
|
})
|
|
It("returns the basic auth Auth and no error", func() {
|
|
auth, err := git.GetAuthFromSecret("test-url.com", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(auth).To(Equal(&httpgit.BasicAuth{
|
|
Username: string(username),
|
|
Password: string(password),
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("SSH auth with not valid git url", func() {
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeSSHAuth,
|
|
}
|
|
})
|
|
It("returns an error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("notavalidurl", secret, "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("failed to parse \"notavalidurl\""))
|
|
Expect(auth).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("SSH auth with not valid ssh key", func() {
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeSSHAuth,
|
|
}
|
|
})
|
|
It("returns an error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("git@github.com:rancher/fleet.git", secret, "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(Equal("ssh: no key found"))
|
|
Expect(auth).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("SSH auth with valid private key and no known_hosts", func() {
|
|
var (
|
|
privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEogIBAAKCAQEAmVh/5bCTwmFU+F7OWyYT6JFkG8V06AdesKSMyeJwT4kGs3Pm
|
|
vKEzKd/CExhd25Tzk5CD8jj6x9usZOtnI0rmCJEgkviWbk6b0K0jPs2b4a6fSbvE
|
|
GpSYheS89cQ7m8YrQr6MuqstjpS1Yz/uWwN0DCrNupyf0GkesqKLlgElPuwcfeQo
|
|
OmyLhY2xViK2ctLzricbKqDMqCFd1WdaYEut6lh3z/Gy/tPk/CJVkP1VGC+KTaer
|
|
vyRKnH9By1VsXUOzOT1NVFjOJfXIqtyTwnE+d24WR9mPcw7kPReiXyS6DoUfcmiq
|
|
4irqaN1smp52iX7EbWsIhir74TrBP51q1M+QFQIDAQABAoIBACTmMNB6bvPFLAcf
|
|
+RPh08SQx8APAZSbwWNMFTy3KkNZO62O5CTbvU4EM9UYde1SqFIH4lg08dOJvrAC
|
|
HS1W5oeFNItpGfmtHL1YDDUekLX7qQS7E/M5coI1imqxL47KXrqO05pPeoTmr8cU
|
|
KSzpZdFPs3WGHsatpN9jUadk2yuKkVEjWxMLOtnvbxuhPSuJmhJ5oajCHhGcGD1k
|
|
RMBsPrvRy9J5nEEfYA+KKpWDhIiS6fP5n0YvroIpcJVU5Fo/EZClNrEBkVl0bOms
|
|
d103PWADAputJrTYpv1b/YsT3STM698DhXsTtvK3RvXouJA+P+JVIO5wnH3ySDhL
|
|
L73FgcECgYEA3ee6oNybgMiL9dXAo4lD2BbKU/fbgXZXZLlxrex1x08u9EjOf2+h
|
|
R7ZvJnmwbTtpwis0mCbkvS6SrMO4qmlTzF0KvWWu6dobBpDUIVBU0w4No9J4c7BS
|
|
lYEGFzJH9VMugZMmstT0Yn9ugntShQjf+gQp6RSr4odssNFqqiOCB8kCgYEAsOgY
|
|
5wHEvR+7v+6iaO+/ZGFgBcmt+B3aC+pwjsyVVpUuAAA3Dyb8T5CIMi44Ys/1C3TC
|
|
b/cypuLFzgdccjqHdZ7z4Tmwo5skUmHTtEsb01mG2FxBBm4a81+FwoN+QRnZDpj6
|
|
JQIfWWmbsP56XPOaqlMYfIhLqBkNwtuvhY4fA+0CgYBwrKJh3cKD0NDoYcHwB9nQ
|
|
FjpkCn2Frg5QEa18T426RyWjWnin0onE/QhRNAb2X+2ibwfEnjMVMFm/qZ3RwauQ
|
|
IEo8wy3ehiWk3tMnmz+G7yLT5SHONGCqkxoBm0FYewUpPAuxUFpKzUPSs0XCUTBR
|
|
Jd4WAK4KVxNEcQFFJMR4qQKBgEc8TrrG1YgqfRneZ/vFftZW96mc+rbMnn7p2oVG
|
|
EGSbEbjiXUl2s2b+ljlOr1nqz4vbamhXrEfTTT+Xazx8IQvWA/KPnndjA49A4VTa
|
|
YcwLYudAztZeA/A4aM5Y0MA6PlNIeoHohuMkSZNOBcvkNEWdzGBpKb34yLfMarNm
|
|
9UpJAoGAU4sGLwlAHGo+PXUi0PyLtQcxcbytkLAQsKMpuZDvNT/KAmu0kJ0p9InN
|
|
5VKnu9SpmXPxjinS8Mg9QXLrfi5SArEllzfXrgW9OU7ht2xandDD+B8S1cmZF+Yz
|
|
1salKM9mBBkl0sWraqtzQSEDjPeAz8P4TpQKn6kIMiZkMnrurvI=
|
|
-----END RSA PRIVATE KEY-----`)
|
|
knownHosts = "foo.bar ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeSSHAuth,
|
|
Data: map[string][]byte{
|
|
corev1.SSHAuthPrivateKey: privateKey,
|
|
},
|
|
}
|
|
})
|
|
|
|
It("returns no error and the ssh auth", func() {
|
|
_, _, publicKey, _, _, err := ssh.ParseKnownHosts([]byte(knownHosts))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
auth, err := git.GetAuthFromSecret("git@github.com:rancher/fleet.git", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
expectedSigner, err := ssh.ParsePrivateKey(privateKey)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
pk, ok := auth.(*gossh.PublicKeys)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(pk.User).To(Equal("git"))
|
|
Expect(pk.Signer).To(Equal(expectedSigner))
|
|
|
|
// No host key enforcing through known hosts
|
|
Expect(pk.HostKeyCallback("foo.baz:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).NotTo(HaveOccurred())
|
|
Expect(pk.HostKeyCallback("foo.bar:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("includes known hosts if provided separately", func() {
|
|
_, _, publicKey, _, _, err := ssh.ParseKnownHosts([]byte(knownHosts))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
url := "git@foo.bar:rancher/fleet.git"
|
|
auth, err := git.GetAuthFromSecret(url, secret, knownHosts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
expectedSigner, err := ssh.ParsePrivateKey(privateKey)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
pk, ok := auth.(*gossh.PublicKeys)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(pk.User).To(Equal("git"))
|
|
Expect(pk.Signer).To(Equal(expectedSigner))
|
|
|
|
Expect(pk.HostKeyCallback).ToNot(BeNil())
|
|
|
|
Expect(pk.HostKeyCallback("foo.baz:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).To(HaveOccurred())
|
|
Expect(pk.HostKeyCallback("foo.bar:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("SSH auth with valid private key and known_hosts", func() {
|
|
var (
|
|
privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEogIBAAKCAQEAmVh/5bCTwmFU+F7OWyYT6JFkG8V06AdesKSMyeJwT4kGs3Pm
|
|
vKEzKd/CExhd25Tzk5CD8jj6x9usZOtnI0rmCJEgkviWbk6b0K0jPs2b4a6fSbvE
|
|
GpSYheS89cQ7m8YrQr6MuqstjpS1Yz/uWwN0DCrNupyf0GkesqKLlgElPuwcfeQo
|
|
OmyLhY2xViK2ctLzricbKqDMqCFd1WdaYEut6lh3z/Gy/tPk/CJVkP1VGC+KTaer
|
|
vyRKnH9By1VsXUOzOT1NVFjOJfXIqtyTwnE+d24WR9mPcw7kPReiXyS6DoUfcmiq
|
|
4irqaN1smp52iX7EbWsIhir74TrBP51q1M+QFQIDAQABAoIBACTmMNB6bvPFLAcf
|
|
+RPh08SQx8APAZSbwWNMFTy3KkNZO62O5CTbvU4EM9UYde1SqFIH4lg08dOJvrAC
|
|
HS1W5oeFNItpGfmtHL1YDDUekLX7qQS7E/M5coI1imqxL47KXrqO05pPeoTmr8cU
|
|
KSzpZdFPs3WGHsatpN9jUadk2yuKkVEjWxMLOtnvbxuhPSuJmhJ5oajCHhGcGD1k
|
|
RMBsPrvRy9J5nEEfYA+KKpWDhIiS6fP5n0YvroIpcJVU5Fo/EZClNrEBkVl0bOms
|
|
d103PWADAputJrTYpv1b/YsT3STM698DhXsTtvK3RvXouJA+P+JVIO5wnH3ySDhL
|
|
L73FgcECgYEA3ee6oNybgMiL9dXAo4lD2BbKU/fbgXZXZLlxrex1x08u9EjOf2+h
|
|
R7ZvJnmwbTtpwis0mCbkvS6SrMO4qmlTzF0KvWWu6dobBpDUIVBU0w4No9J4c7BS
|
|
lYEGFzJH9VMugZMmstT0Yn9ugntShQjf+gQp6RSr4odssNFqqiOCB8kCgYEAsOgY
|
|
5wHEvR+7v+6iaO+/ZGFgBcmt+B3aC+pwjsyVVpUuAAA3Dyb8T5CIMi44Ys/1C3TC
|
|
b/cypuLFzgdccjqHdZ7z4Tmwo5skUmHTtEsb01mG2FxBBm4a81+FwoN+QRnZDpj6
|
|
JQIfWWmbsP56XPOaqlMYfIhLqBkNwtuvhY4fA+0CgYBwrKJh3cKD0NDoYcHwB9nQ
|
|
FjpkCn2Frg5QEa18T426RyWjWnin0onE/QhRNAb2X+2ibwfEnjMVMFm/qZ3RwauQ
|
|
IEo8wy3ehiWk3tMnmz+G7yLT5SHONGCqkxoBm0FYewUpPAuxUFpKzUPSs0XCUTBR
|
|
Jd4WAK4KVxNEcQFFJMR4qQKBgEc8TrrG1YgqfRneZ/vFftZW96mc+rbMnn7p2oVG
|
|
EGSbEbjiXUl2s2b+ljlOr1nqz4vbamhXrEfTTT+Xazx8IQvWA/KPnndjA49A4VTa
|
|
YcwLYudAztZeA/A4aM5Y0MA6PlNIeoHohuMkSZNOBcvkNEWdzGBpKb34yLfMarNm
|
|
9UpJAoGAU4sGLwlAHGo+PXUi0PyLtQcxcbytkLAQsKMpuZDvNT/KAmu0kJ0p9InN
|
|
5VKnu9SpmXPxjinS8Mg9QXLrfi5SArEllzfXrgW9OU7ht2xandDD+B8S1cmZF+Yz
|
|
1salKM9mBBkl0sWraqtzQSEDjPeAz8P4TpQKn6kIMiZkMnrurvI=
|
|
-----END RSA PRIVATE KEY-----`)
|
|
fingerPrint = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOLWGeeq/e1mK/zH47UeQeMtdh+NEz6j7xp5cAINcV2pPWgAsuyh5dumMv1RkC1rr0pmWekCoMnR2c4+PllRqrQ="
|
|
)
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeSSHAuth,
|
|
Data: map[string][]byte{
|
|
corev1.SSHAuthPrivateKey: privateKey,
|
|
"known_hosts": []byte("[localhost]:22 " + fingerPrint),
|
|
},
|
|
}
|
|
})
|
|
|
|
It("returns no error and the ssh auth", func() {
|
|
auth, err := git.GetAuthFromSecret("git@github.com:rancher/fleet.git", secret, "")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
expectedSigner, err := ssh.ParsePrivateKey(privateKey)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
pk, ok := auth.(*gossh.PublicKeys)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(pk.User).To(Equal("git"))
|
|
Expect(pk.Signer).To(Equal(expectedSigner))
|
|
|
|
// Check that host keys are enforced through known hosts
|
|
_, _, publicKey, _, _, err := ssh.ParseKnownHosts([]byte(fmt.Sprintf("[localhost] %s", fingerPrint)))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(pk.HostKeyCallback).ToNot(BeNil())
|
|
Expect(pk.HostKeyCallback("foo.baz:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).To(HaveOccurred())
|
|
Expect(pk.HostKeyCallback("[localhost]:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("ignores known hosts provided separately", func() {
|
|
knownHosts := "foo.bar ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"
|
|
auth, err := git.GetAuthFromSecret("git@github.com:rancher/fleet.git", secret, knownHosts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
expectedSigner, err := ssh.ParsePrivateKey(privateKey)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
pk, ok := auth.(*gossh.PublicKeys)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(pk.User).To(Equal("git"))
|
|
Expect(pk.Signer).To(Equal(expectedSigner))
|
|
|
|
// Check that host keys are enforced through known hosts
|
|
_, _, publicKey, _, _, err := ssh.ParseKnownHosts([]byte(fmt.Sprintf("[localhost] %s", fingerPrint)))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(pk.HostKeyCallback).ToNot(BeNil())
|
|
Expect(pk.HostKeyCallback("foo.bar:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).To(HaveOccurred())
|
|
Expect(pk.HostKeyCallback("[localhost]:22", mockAddr{ip: "8.8.8.8:22"}, publicKey)).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("SSH auth with valid private key and invalid known_hosts", func() {
|
|
var (
|
|
privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEogIBAAKCAQEAmVh/5bCTwmFU+F7OWyYT6JFkG8V06AdesKSMyeJwT4kGs3Pm
|
|
vKEzKd/CExhd25Tzk5CD8jj6x9usZOtnI0rmCJEgkviWbk6b0K0jPs2b4a6fSbvE
|
|
GpSYheS89cQ7m8YrQr6MuqstjpS1Yz/uWwN0DCrNupyf0GkesqKLlgElPuwcfeQo
|
|
OmyLhY2xViK2ctLzricbKqDMqCFd1WdaYEut6lh3z/Gy/tPk/CJVkP1VGC+KTaer
|
|
vyRKnH9By1VsXUOzOT1NVFjOJfXIqtyTwnE+d24WR9mPcw7kPReiXyS6DoUfcmiq
|
|
4irqaN1smp52iX7EbWsIhir74TrBP51q1M+QFQIDAQABAoIBACTmMNB6bvPFLAcf
|
|
+RPh08SQx8APAZSbwWNMFTy3KkNZO62O5CTbvU4EM9UYde1SqFIH4lg08dOJvrAC
|
|
HS1W5oeFNItpGfmtHL1YDDUekLX7qQS7E/M5coI1imqxL47KXrqO05pPeoTmr8cU
|
|
KSzpZdFPs3WGHsatpN9jUadk2yuKkVEjWxMLOtnvbxuhPSuJmhJ5oajCHhGcGD1k
|
|
RMBsPrvRy9J5nEEfYA+KKpWDhIiS6fP5n0YvroIpcJVU5Fo/EZClNrEBkVl0bOms
|
|
d103PWADAputJrTYpv1b/YsT3STM698DhXsTtvK3RvXouJA+P+JVIO5wnH3ySDhL
|
|
L73FgcECgYEA3ee6oNybgMiL9dXAo4lD2BbKU/fbgXZXZLlxrex1x08u9EjOf2+h
|
|
R7ZvJnmwbTtpwis0mCbkvS6SrMO4qmlTzF0KvWWu6dobBpDUIVBU0w4No9J4c7BS
|
|
lYEGFzJH9VMugZMmstT0Yn9ugntShQjf+gQp6RSr4odssNFqqiOCB8kCgYEAsOgY
|
|
5wHEvR+7v+6iaO+/ZGFgBcmt+B3aC+pwjsyVVpUuAAA3Dyb8T5CIMi44Ys/1C3TC
|
|
b/cypuLFzgdccjqHdZ7z4Tmwo5skUmHTtEsb01mG2FxBBm4a81+FwoN+QRnZDpj6
|
|
JQIfWWmbsP56XPOaqlMYfIhLqBkNwtuvhY4fA+0CgYBwrKJh3cKD0NDoYcHwB9nQ
|
|
FjpkCn2Frg5QEa18T426RyWjWnin0onE/QhRNAb2X+2ibwfEnjMVMFm/qZ3RwauQ
|
|
IEo8wy3ehiWk3tMnmz+G7yLT5SHONGCqkxoBm0FYewUpPAuxUFpKzUPSs0XCUTBR
|
|
Jd4WAK4KVxNEcQFFJMR4qQKBgEc8TrrG1YgqfRneZ/vFftZW96mc+rbMnn7p2oVG
|
|
EGSbEbjiXUl2s2b+ljlOr1nqz4vbamhXrEfTTT+Xazx8IQvWA/KPnndjA49A4VTa
|
|
YcwLYudAztZeA/A4aM5Y0MA6PlNIeoHohuMkSZNOBcvkNEWdzGBpKb34yLfMarNm
|
|
9UpJAoGAU4sGLwlAHGo+PXUi0PyLtQcxcbytkLAQsKMpuZDvNT/KAmu0kJ0p9InN
|
|
5VKnu9SpmXPxjinS8Mg9QXLrfi5SArEllzfXrgW9OU7ht2xandDD+B8S1cmZF+Yz
|
|
1salKM9mBBkl0sWraqtzQSEDjPeAz8P4TpQKn6kIMiZkMnrurvI=
|
|
-----END RSA PRIVATE KEY-----`)
|
|
)
|
|
BeforeEach(func() {
|
|
secret = &corev1.Secret{
|
|
Type: corev1.SecretTypeSSHAuth,
|
|
Data: map[string][]byte{
|
|
corev1.SSHAuthPrivateKey: privateKey,
|
|
"known_hosts": []byte("Not_valid_known_hosts"),
|
|
},
|
|
}
|
|
})
|
|
It("returns an error and no auth", func() {
|
|
auth, err := git.GetAuthFromSecret("git@github.com:rancher/fleet.git", secret, "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(auth).To(BeNil())
|
|
Expect(err.Error()).To(ContainSubstring("knownhosts: missing host pattern"))
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("git's GetHTTPClientFromSecret tests", func() {
|
|
When("using a nil secret, no caBudle and InsecureSkipVerify = false", func() {
|
|
var caBundle []byte
|
|
client, err := git.GetHTTPClientFromSecret(nil, caBundle, false, gitClientTimeout)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(client).ToNot(BeNil())
|
|
expectedTransport, ok := client.Transport.(*http.Transport)
|
|
Expect(ok).To(BeTrue())
|
|
|
|
It("returns a client's transport with InsecureSkipVerify = false", func() {
|
|
Expect(expectedTransport.TLSClientConfig.InsecureSkipVerify).To(BeFalse())
|
|
})
|
|
|
|
It("returns a client's transport with no credentials", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
req := &http.Request{}
|
|
_, _ = client.Transport.RoundTrip(req)
|
|
auth := req.Header.Get("Authorization")
|
|
Expect(auth).To(BeEmpty())
|
|
})
|
|
|
|
It("returns a client's transport no rootCAs", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
Expect(expectedTransport.TLSClientConfig.RootCAs).To(BeNil())
|
|
})
|
|
})
|
|
|
|
When("using a nil secret, no caBudle and InsecureSkipVerify = true", func() {
|
|
var caBundle []byte
|
|
client, err := git.GetHTTPClientFromSecret(nil, caBundle, true, gitClientTimeout)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(client).ToNot(BeNil())
|
|
expectedTransport, ok := client.Transport.(*http.Transport)
|
|
Expect(ok).To(BeTrue())
|
|
|
|
It("returns a client's transport with InsecureSkipVerify = true", func() {
|
|
Expect(expectedTransport.TLSClientConfig.InsecureSkipVerify).To(BeTrue())
|
|
})
|
|
|
|
It("returns a client's transport with no credentials", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
req := &http.Request{}
|
|
_, _ = client.Transport.RoundTrip(req)
|
|
auth := req.Header.Get("Authorization")
|
|
Expect(auth).To(BeEmpty())
|
|
})
|
|
|
|
It("returns a client's transport no rootCAs", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
Expect(expectedTransport.TLSClientConfig.RootCAs).To(BeNil())
|
|
})
|
|
})
|
|
|
|
When("using a nil secret, caBudle and InsecureSkipVerify = true", func() {
|
|
caBundlePEM := []byte(`-----BEGIN CERTIFICATE-----
|
|
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
|
|
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
|
|
Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
|
|
MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
|
|
bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
|
|
ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
|
|
7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
|
|
0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
|
|
B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
|
|
BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
|
|
LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
|
|
DXZDjC5Ty3zfDBeWUA==
|
|
-----END CERTIFICATE-----`)
|
|
|
|
block, _ := pem.Decode([]byte(caBundlePEM))
|
|
Expect(block).ToNot(BeNil())
|
|
client, err := git.GetHTTPClientFromSecret(nil, block.Bytes, true, gitClientTimeout)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(client).ToNot(BeNil())
|
|
expectedTransport, ok := client.Transport.(*http.Transport)
|
|
Expect(ok).To(BeTrue())
|
|
|
|
It("returns a client's transport with InsecureSkipVerify = true", func() {
|
|
Expect(expectedTransport.TLSClientConfig.InsecureSkipVerify).To(BeTrue())
|
|
})
|
|
|
|
It("returns a client's transport with no credentials", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
req := &http.Request{}
|
|
_, _ = client.Transport.RoundTrip(req)
|
|
auth := req.Header.Get("Authorization")
|
|
Expect(auth).To(BeEmpty())
|
|
})
|
|
|
|
It("returns a client's transport no rootCAs", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
Expect(expectedTransport.TLSClientConfig.RootCAs).ToNot(BeNil())
|
|
})
|
|
})
|
|
|
|
When("using a malformed ca bundle", func() {
|
|
caBundle := []byte(`-----BEGIN CERTIFICATE-----
|
|
SUPER FAKE CERT
|
|
-----END CERTIFICATE-----`)
|
|
client, err := git.GetHTTPClientFromSecret(nil, caBundle, true, gitClientTimeout)
|
|
It("returns an error", func() {
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(client).To(BeNil())
|
|
Expect(err.Error()).To(Equal("x509: malformed certificate"))
|
|
})
|
|
})
|
|
|
|
When("using a valid basic auth secret", func() {
|
|
username := []byte("superuser")
|
|
password := []byte("supersecure")
|
|
secret := &corev1.Secret{
|
|
Type: corev1.SecretTypeBasicAuth,
|
|
Data: map[string][]byte{
|
|
corev1.BasicAuthUsernameKey: username,
|
|
corev1.BasicAuthPasswordKey: password,
|
|
},
|
|
}
|
|
client, err := git.GetHTTPClientFromSecret(secret, nil, false, gitClientTimeout)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(client).ToNot(BeNil())
|
|
|
|
It("returns a client's transport roundtrip that sets credentials", func() {
|
|
// no auth is set by the transport RoundTrip
|
|
req := &http.Request{}
|
|
req.Header = make(map[string][]string)
|
|
_, _ = client.Transport.RoundTrip(req)
|
|
auth := req.Header.Get("Authorization")
|
|
Expect(auth).To(Equal("Basic c3VwZXJ1c2VyOnN1cGVyc2VjdXJl"))
|
|
})
|
|
})
|
|
|
|
When("using a valid tls secret", func() {
|
|
const certificatePEM = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBLjCB4aADAgECAhAX0YGTviqMISAQJRXoNCNPMAUGAytlcDASMRAwDgYDVQQK
|
|
EwdBY21lIENvMB4XDTE5MDUxNjIxNTQyNloXDTIwMDUxNTIxNTQyNlowEjEQMA4G
|
|
A1UEChMHQWNtZSBDbzAqMAUGAytlcAMhAAvgtWC14nkwPb7jHuBQsQTIbcd4bGkv
|
|
xRStmmNveRKRo00wSzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
|
|
AwIwDAYDVR0TAQH/BAIwADAWBgNVHREEDzANggtleGFtcGxlLmNvbTAFBgMrZXAD
|
|
QQD8GRcqlKUx+inILn9boF2KTjRAOdazENwZ/qAicbP1j6FYDc308YUkv+Y9FN/f
|
|
7Q7hF9gRomDQijcjKsJGqjoI
|
|
-----END CERTIFICATE-----`
|
|
|
|
var keyPEM = `
|
|
-----BEGIN PRIVATE KEY-----
|
|
MC4CAQAwBQYDK2VwBCIEINifzf07d9qx3d44e0FSbV4mC/xQxT644RRbpgNpin7I
|
|
-----END PRIVATE KEY-----`
|
|
secret := &corev1.Secret{
|
|
Type: corev1.SecretTypeTLS,
|
|
Data: map[string][]byte{
|
|
corev1.TLSCertKey: []byte(certificatePEM),
|
|
corev1.TLSPrivateKeyKey: []byte(keyPEM),
|
|
},
|
|
}
|
|
client, err := git.GetHTTPClientFromSecret(secret, nil, false, gitClientTimeout)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(client).ToNot(BeNil())
|
|
|
|
It("returns a client's transport with certificates", func() {
|
|
expectedTransport, ok := client.Transport.(*http.Transport)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(expectedTransport.TLSClientConfig.Certificates).ToNot(BeEmpty())
|
|
})
|
|
})
|
|
|
|
When("using a non valid tls secret", func() {
|
|
const certificatePEM = `
|
|
-----BEGIN CERTIFICATE-----
|
|
THIS IS NOT A VALID CERTIFICATE
|
|
-----END CERTIFICATE-----`
|
|
|
|
var keyPEM = `
|
|
-----BEGIN PRIVATE KEY-----
|
|
THIS IS NOT A VALID KEY
|
|
-----END PRIVATE KEY-----`
|
|
secret := &corev1.Secret{
|
|
Type: corev1.SecretTypeTLS,
|
|
Data: map[string][]byte{
|
|
corev1.TLSCertKey: []byte(certificatePEM),
|
|
corev1.TLSPrivateKeyKey: []byte(keyPEM),
|
|
},
|
|
}
|
|
_, err := git.GetHTTPClientFromSecret(secret, nil, false, gitClientTimeout)
|
|
|
|
It("returns an error", func() {
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(Equal("tls: failed to find any PEM data in certificate input"))
|
|
})
|
|
})
|
|
})
|
|
|
|
type mockAddr struct {
|
|
ip string
|
|
}
|
|
|
|
func (ma mockAddr) Network() string { return "tcp" }
|
|
func (ma mockAddr) String() string { return ma.ip }
|