proxy_endpoints_remote_check_make.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "fmt"
  18. "math/rand"
  19. "net"
  20. "time"
  21. "yunion.io/x/log"
  22. cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy"
  23. "yunion.io/x/onecloud/pkg/httperrors"
  24. "yunion.io/x/onecloud/pkg/mcclient"
  25. "yunion.io/x/onecloud/pkg/mcclient/auth"
  26. ansible_modules "yunion.io/x/onecloud/pkg/mcclient/modules/ansible"
  27. "yunion.io/x/onecloud/pkg/util/ansible"
  28. ssh_util "yunion.io/x/onecloud/pkg/util/ssh"
  29. )
  30. func (proxyendpoint *SProxyEndpoint) remoteCheckMake(ctx context.Context, userCred mcclient.TokenCredential) error {
  31. ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
  32. defer cancel()
  33. conf := ssh_util.ClientConfig{
  34. Username: proxyendpoint.User,
  35. Host: proxyendpoint.Host,
  36. Port: proxyendpoint.Port,
  37. PrivateKey: proxyendpoint.PrivateKey,
  38. }
  39. client, err := conf.ConnectContext(ctx)
  40. if err != nil {
  41. return httperrors.NewBadRequestError("ssh connect failed: %v", err)
  42. }
  43. defer client.Close()
  44. if err := proxyendpoint.remoteConfigure(ctx, userCred); err != nil {
  45. return err
  46. }
  47. // total wait time
  48. tmo := time.NewTimer(23 * time.Second)
  49. tmoErr := httperrors.NewOutOfResourceError("timeout testing remote config. please retry later")
  50. // find a remote port for bind and listen
  51. var (
  52. listenAddr string
  53. listener net.Listener
  54. )
  55. portMin := cloudproxy_api.BindPortMax + 1
  56. portTotal := 65535 - cloudproxy_api.BindPortMax
  57. portReqStart := rand.Intn(portTotal)
  58. for portInc := portReqStart; ; {
  59. port := portMin + portInc
  60. addr := net.JoinHostPort(proxyendpoint.IntranetIpAddr, fmt.Sprintf("%d", port))
  61. listener_, err := client.Listen("tcp", addr)
  62. if err == nil {
  63. // we assume the port is not occupied by intranet addr,
  64. // even though there is the possibility that the above
  65. // test only happen against 127.0.0.1
  66. listenAddr = addr
  67. listener = listener_
  68. break
  69. }
  70. select {
  71. case <-tmo.C:
  72. return tmoErr
  73. default:
  74. }
  75. portInc += 1
  76. if portInc == portTotal {
  77. portInc = 0
  78. }
  79. if portInc == portReqStart {
  80. return httperrors.NewOutOfResourceError("no available port for bind test")
  81. }
  82. }
  83. // test for good news
  84. for {
  85. if listener != nil {
  86. if conn, err := client.Dial("tcp", listenAddr); err == nil {
  87. conn.Close()
  88. listener.Close()
  89. return nil
  90. }
  91. listener.Close()
  92. }
  93. var err error
  94. listener, err = client.Listen("tcp", listenAddr)
  95. if err != nil {
  96. log.Warningf("retry ssh listen %s: %v", listenAddr, err)
  97. }
  98. select {
  99. case <-tmo.C:
  100. return tmoErr
  101. case <-time.After(2 * time.Second):
  102. }
  103. }
  104. // return httperrors.NewConflictError("remote sshd_config may have a problem with GatewayPorts")
  105. }
  106. func (proxyendpoint *SProxyEndpoint) remoteConfigure(ctx context.Context, userCred mcclient.TokenCredential) error {
  107. host := ansible.Host{
  108. Name: "0.0.0.0", // ansibleserver requires this field to be either ip_addr, or name of guest, host
  109. }
  110. host.SetVar("ansible_user", proxyendpoint.User)
  111. host.SetVar("ansible_host", proxyendpoint.Host)
  112. host.SetVar("ansible_port", fmt.Sprintf("%d", proxyendpoint.Port))
  113. host.SetVar("ansible_become", "yes")
  114. pb := &ansible.Playbook{
  115. PrivateKey: []byte(proxyendpoint.PrivateKey),
  116. Inventory: ansible.Inventory{
  117. Hosts: []ansible.Host{host},
  118. },
  119. Modules: []ansible.Module{
  120. {
  121. Name: "lineinfile",
  122. Args: []string{
  123. "dest=/etc/ssh/sshd_config",
  124. "state=present",
  125. fmt.Sprintf("regexp=%q", "^GatewayPorts "),
  126. fmt.Sprintf("line=%q", "GatewayPorts clientspecified"),
  127. fmt.Sprintf("validate=%q", "sshd -T -f %s"),
  128. },
  129. },
  130. {
  131. Name: "service",
  132. Args: []string{
  133. "name=sshd",
  134. "state=restarted",
  135. },
  136. },
  137. {
  138. Name: "service",
  139. Args: []string{
  140. "name=ssh", // ubuntu uses this name. It can fail for centos, but we do not care
  141. "state=restarted",
  142. },
  143. },
  144. },
  145. }
  146. cliSess := auth.GetSession(ctx, userCred, "")
  147. pbId := ""
  148. pbName := "pe-remote-configure-" + proxyendpoint.Name
  149. _, err := ansible_modules.AnsiblePlaybooks.UpdateOrCreatePbModel(
  150. ctx, cliSess, pbId, pbName, pb,
  151. )
  152. if err != nil {
  153. return httperrors.NewGeneralError(err)
  154. }
  155. return nil
  156. }