You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

349 lines
9.7 KiB

  1. // Copyright (c) 2015-2024 MinIO, Inc.
  2. //
  3. // This file is part of MinIO Object Storage stack
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. package cmd
  18. import (
  19. "context"
  20. "errors"
  21. "fmt"
  22. "net"
  23. "os"
  24. "testing"
  25. "github.com/minio/madmin-go/v3"
  26. "golang.org/x/crypto/ssh"
  27. )
  28. type MockConnMeta struct {
  29. username string
  30. }
  31. func (m *MockConnMeta) User() string {
  32. return m.username
  33. }
  34. func (m *MockConnMeta) SessionID() []byte {
  35. return []byte{}
  36. }
  37. func (m *MockConnMeta) ClientVersion() []byte {
  38. return []byte{}
  39. }
  40. func (m *MockConnMeta) ServerVersion() []byte {
  41. return []byte{}
  42. }
  43. func (m *MockConnMeta) RemoteAddr() net.Addr {
  44. return nil
  45. }
  46. func (m *MockConnMeta) LocalAddr() net.Addr {
  47. return nil
  48. }
  49. func newSSHConnMock(username string) ssh.ConnMetadata {
  50. return &MockConnMeta{username: username}
  51. }
  52. func TestSFTPAuthentication(t *testing.T) {
  53. for i, testCase := range iamTestSuites {
  54. t.Run(
  55. fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.ServerTypeDescription),
  56. func(t *testing.T) {
  57. c := &check{t, testCase.serverType}
  58. suite := testCase
  59. suite.SetUpSuite(c)
  60. suite.SFTPServiceAccountLogin(c)
  61. suite.SFTPInvalidServiceAccountPassword(c)
  62. // LDAP tests
  63. ldapServer := os.Getenv(EnvTestLDAPServer)
  64. if ldapServer == "" {
  65. c.Skipf("Skipping LDAP test as no LDAP server is provided via %s", EnvTestLDAPServer)
  66. }
  67. suite.SetUpLDAP(c, ldapServer)
  68. suite.SFTPFailedAuthDueToMissingPolicy(c)
  69. suite.SFTPFailedAuthDueToInvalidUser(c)
  70. suite.SFTPFailedForcedServiceAccountAuthOnLDAPUser(c)
  71. suite.SFTPFailedAuthDueToInvalidPassword(c)
  72. suite.SFTPValidLDAPLoginWithPassword(c)
  73. suite.SFTPPublicKeyAuthentication(c)
  74. suite.SFTPFailedPublicKeyAuthenticationInvalidKey(c)
  75. suite.SFTPPublicKeyAuthNoPubKey(c)
  76. suite.TearDownSuite(c)
  77. },
  78. )
  79. }
  80. }
  81. func (s *TestSuiteIAM) SFTPFailedPublicKeyAuthenticationInvalidKey(c *check) {
  82. keyBytes, err := os.ReadFile("./testdata/invalid_test_key.pub")
  83. if err != nil {
  84. c.Fatalf("could not read test key file: %s", err)
  85. }
  86. testKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
  87. if err != nil {
  88. c.Fatalf("could not parse test key file: %s", err)
  89. }
  90. newSSHCon := newSSHConnMock("dillon=ldap")
  91. _, err = sshPubKeyAuth(newSSHCon, testKey)
  92. if err == nil || !errors.Is(err, errAuthentication) {
  93. c.Fatalf("expected err(%s) but got (%s)", errAuthentication, err)
  94. }
  95. newSSHCon = newSSHConnMock("dillon")
  96. _, err = sshPubKeyAuth(newSSHCon, testKey)
  97. if err == nil || !errors.Is(err, errNoSuchUser) {
  98. c.Fatalf("expected err(%s) but got (%s)", errNoSuchUser, err)
  99. }
  100. }
  101. func (s *TestSuiteIAM) SFTPPublicKeyAuthentication(c *check) {
  102. keyBytes, err := os.ReadFile("./testdata/dillon_test_key.pub")
  103. if err != nil {
  104. c.Fatalf("could not read test key file: %s", err)
  105. }
  106. testKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
  107. if err != nil {
  108. c.Fatalf("could not parse test key file: %s", err)
  109. }
  110. newSSHCon := newSSHConnMock("dillon=ldap")
  111. _, err = sshPubKeyAuth(newSSHCon, testKey)
  112. if err != nil {
  113. c.Fatalf("expected no error but got(%s)", err)
  114. }
  115. newSSHCon = newSSHConnMock("dillon")
  116. _, err = sshPubKeyAuth(newSSHCon, testKey)
  117. if err != nil {
  118. c.Fatalf("expected no error but got(%s)", err)
  119. }
  120. }
  121. // A user without an sshpubkey attribute in LDAP (here: fahim) should not be
  122. // able to authenticate.
  123. func (s *TestSuiteIAM) SFTPPublicKeyAuthNoPubKey(c *check) {
  124. keyBytes, err := os.ReadFile("./testdata/dillon_test_key.pub")
  125. if err != nil {
  126. c.Fatalf("could not read test key file: %s", err)
  127. }
  128. testKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
  129. if err != nil {
  130. c.Fatalf("could not parse test key file: %s", err)
  131. }
  132. newSSHCon := newSSHConnMock("fahim=ldap")
  133. _, err = sshPubKeyAuth(newSSHCon, testKey)
  134. if err == nil {
  135. c.Fatalf("expected error but got none")
  136. }
  137. newSSHCon = newSSHConnMock("fahim")
  138. _, err = sshPubKeyAuth(newSSHCon, testKey)
  139. if err == nil {
  140. c.Fatalf("expected error but got none")
  141. }
  142. }
  143. func (s *TestSuiteIAM) SFTPFailedAuthDueToMissingPolicy(c *check) {
  144. newSSHCon := newSSHConnMock("dillon=ldap")
  145. _, err := sshPasswordAuth(newSSHCon, []byte("dillon"))
  146. if err == nil || !errors.Is(err, errSFTPUserHasNoPolicies) {
  147. c.Fatalf("expected err(%s) but got (%s)", errSFTPUserHasNoPolicies, err)
  148. }
  149. newSSHCon = newSSHConnMock("dillon")
  150. _, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
  151. if err == nil || !errors.Is(err, errNoSuchUser) {
  152. c.Fatalf("expected err(%s) but got (%s)", errNoSuchUser, err)
  153. }
  154. }
  155. func (s *TestSuiteIAM) SFTPFailedAuthDueToInvalidUser(c *check) {
  156. newSSHCon := newSSHConnMock("dillon_error")
  157. _, err := sshPasswordAuth(newSSHCon, []byte("dillon_error"))
  158. if err == nil || !errors.Is(err, errNoSuchUser) {
  159. c.Fatalf("expected err(%s) but got (%s)", errNoSuchUser, err)
  160. }
  161. }
  162. func (s *TestSuiteIAM) SFTPFailedForcedServiceAccountAuthOnLDAPUser(c *check) {
  163. newSSHCon := newSSHConnMock("dillon=svc")
  164. _, err := sshPasswordAuth(newSSHCon, []byte("dillon"))
  165. if err == nil || !errors.Is(err, errNoSuchUser) {
  166. c.Fatalf("expected err(%s) but got (%s)", errNoSuchUser, err)
  167. }
  168. }
  169. func (s *TestSuiteIAM) SFTPFailedAuthDueToInvalidPassword(c *check) {
  170. newSSHCon := newSSHConnMock("dillon")
  171. _, err := sshPasswordAuth(newSSHCon, []byte("dillon_error"))
  172. if err == nil || !errors.Is(err, errNoSuchUser) {
  173. c.Fatalf("expected err(%s) but got (%s)", errNoSuchUser, err)
  174. }
  175. }
  176. func (s *TestSuiteIAM) SFTPInvalidServiceAccountPassword(c *check) {
  177. ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
  178. defer cancel()
  179. accessKey, secretKey := mustGenerateCredentials(c)
  180. err := s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
  181. if err != nil {
  182. c.Fatalf("Unable to set user: %v", err)
  183. }
  184. userReq := madmin.PolicyAssociationReq{
  185. Policies: []string{"readwrite"},
  186. User: accessKey,
  187. }
  188. if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
  189. c.Fatalf("Unable to attach policy: %v", err)
  190. }
  191. newSSHCon := newSSHConnMock(accessKey + "=svc")
  192. _, err = sshPasswordAuth(newSSHCon, []byte("invalid"))
  193. if err == nil || !errors.Is(err, errAuthentication) {
  194. c.Fatalf("expected err(%s) but got (%s)", errAuthentication, err)
  195. }
  196. newSSHCon = newSSHConnMock(accessKey)
  197. _, err = sshPasswordAuth(newSSHCon, []byte("invalid"))
  198. if err == nil || !errors.Is(err, errAuthentication) {
  199. c.Fatalf("expected err(%s) but got (%s)", errAuthentication, err)
  200. }
  201. }
  202. func (s *TestSuiteIAM) SFTPServiceAccountLogin(c *check) {
  203. ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
  204. defer cancel()
  205. accessKey, secretKey := mustGenerateCredentials(c)
  206. err := s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
  207. if err != nil {
  208. c.Fatalf("Unable to set user: %v", err)
  209. }
  210. userReq := madmin.PolicyAssociationReq{
  211. Policies: []string{"readwrite"},
  212. User: accessKey,
  213. }
  214. if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
  215. c.Fatalf("Unable to attach policy: %v", err)
  216. }
  217. newSSHCon := newSSHConnMock(accessKey + "=svc")
  218. _, err = sshPasswordAuth(newSSHCon, []byte(secretKey))
  219. if err != nil {
  220. c.Fatalf("expected no error but got (%s)", err)
  221. }
  222. newSSHCon = newSSHConnMock(accessKey)
  223. _, err = sshPasswordAuth(newSSHCon, []byte(secretKey))
  224. if err != nil {
  225. c.Fatalf("expected no error but got (%s)", err)
  226. }
  227. }
  228. func (s *TestSuiteIAM) SFTPValidLDAPLoginWithPassword(c *check) {
  229. ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
  230. defer cancel()
  231. // we need to do this so that the user has a policy before authentication.
  232. // ldap user accounts without policies are denied access in sftp.
  233. policy := "mypolicy"
  234. policyBytes := []byte(`{
  235. "Version": "2012-10-17",
  236. "Statement": [
  237. {
  238. "Effect": "Allow",
  239. "Action": [
  240. "s3:PutObject",
  241. "s3:GetObject",
  242. "s3:ListBucket"
  243. ],
  244. "Resource": [
  245. "arn:aws:s3:::BUCKET/*"
  246. ]
  247. }
  248. ]
  249. }`)
  250. err := s.adm.AddCannedPolicy(ctx, policy, policyBytes)
  251. if err != nil {
  252. c.Fatalf("policy add error: %v", err)
  253. }
  254. {
  255. userDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
  256. userReq := madmin.PolicyAssociationReq{
  257. Policies: []string{policy},
  258. User: userDN,
  259. }
  260. if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
  261. c.Fatalf("Unable to attach policy: %v", err)
  262. }
  263. newSSHCon := newSSHConnMock("dillon=ldap")
  264. _, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
  265. if err != nil {
  266. c.Fatal("Password authentication failed for user (dillon):", err)
  267. }
  268. newSSHCon = newSSHConnMock("dillon")
  269. _, err = sshPasswordAuth(newSSHCon, []byte("dillon"))
  270. if err != nil {
  271. c.Fatal("Password authentication failed for user (dillon):", err)
  272. }
  273. }
  274. {
  275. userDN := "uid=fahim,ou=people,ou=swengg,dc=min,dc=io"
  276. userReq := madmin.PolicyAssociationReq{
  277. Policies: []string{policy},
  278. User: userDN,
  279. }
  280. if _, err := s.adm.AttachPolicyLDAP(ctx, userReq); err != nil {
  281. c.Fatalf("Unable to attach policy: %v", err)
  282. }
  283. newSSHCon := newSSHConnMock("fahim=ldap")
  284. _, err = sshPasswordAuth(newSSHCon, []byte("fahim"))
  285. if err != nil {
  286. c.Fatal("Password authentication failed for user (fahim):", err)
  287. }
  288. newSSHCon = newSSHConnMock("fahim")
  289. _, err = sshPasswordAuth(newSSHCon, []byte("fahim"))
  290. if err != nil {
  291. c.Fatal("Password authentication failed for user (fahim):", err)
  292. }
  293. }
  294. }