mcclient.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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 mcclient
  15. import (
  16. "context"
  17. "crypto/tls"
  18. "fmt"
  19. "io"
  20. "net/http"
  21. "strings"
  22. "time"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/appctx"
  26. "yunion.io/x/pkg/errors"
  27. "yunion.io/x/pkg/gotypes"
  28. "yunion.io/x/pkg/util/httputils"
  29. "yunion.io/x/pkg/util/rbacscope"
  30. "yunion.io/x/onecloud/pkg/apis"
  31. api "yunion.io/x/onecloud/pkg/apis/identity"
  32. "yunion.io/x/onecloud/pkg/appsrv"
  33. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  34. "yunion.io/x/onecloud/pkg/httperrors"
  35. "yunion.io/x/onecloud/pkg/util/rbacutils"
  36. "yunion.io/x/onecloud/pkg/util/seclib2"
  37. )
  38. var listenerWorker *appsrv.SWorkerManager
  39. type Client struct {
  40. authUrl string
  41. timeout int
  42. debug bool
  43. httpconn *http.Client
  44. _serviceCatalog IServiceCatalog
  45. catalogListeners []IServiceCatalogChangeListener
  46. }
  47. func init() {
  48. listenerWorker = appsrv.NewWorkerManager("client_catalog_listener_worker", 1, 2048, false)
  49. }
  50. func NewClient(authUrl string, timeout int, debug bool, insecure bool, certFile, keyFile string) *Client {
  51. var tlsConf *tls.Config
  52. if len(certFile) > 0 && len(keyFile) > 0 {
  53. var err error
  54. tlsConf, err = seclib2.InitTLSConfig(certFile, keyFile)
  55. if err != nil {
  56. log.Errorf("load TLS failed %s", err)
  57. }
  58. }
  59. if tlsConf == nil || gotypes.IsNil(tlsConf) {
  60. tlsConf = &tls.Config{}
  61. }
  62. tlsConf.InsecureSkipVerify = insecure
  63. tr := httputils.GetTransport(insecure)
  64. tr.TLSClientConfig = tlsConf
  65. tr.IdleConnTimeout = 5 * time.Second
  66. tr.TLSHandshakeTimeout = 10 * time.Second
  67. tr.ResponseHeaderTimeout = 0
  68. client := Client{authUrl: authUrl,
  69. timeout: timeout,
  70. debug: debug,
  71. httpconn: &http.Client{
  72. Transport: tr,
  73. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  74. return http.ErrUseLastResponse
  75. }, // 不自动处理重定向请求
  76. },
  77. }
  78. return &client
  79. }
  80. func (client *Client) HttpClient() *http.Client {
  81. return client.httpconn
  82. }
  83. func (client *Client) SetHttpTransportProxyFunc(proxyFunc httputils.TransportProxyFunc) {
  84. httputils.SetClientProxyFunc(client.httpconn, proxyFunc)
  85. }
  86. func (client *Client) GetClient() *http.Client {
  87. return client.httpconn
  88. }
  89. func (client *Client) SetTransport(ts http.RoundTripper) {
  90. client.httpconn.Transport = ts
  91. }
  92. func (client *Client) SetDebug(debug bool) {
  93. client.debug = debug
  94. }
  95. func (client *Client) GetDebug() bool {
  96. return client.debug
  97. }
  98. func (client *Client) AuthVersion() string {
  99. pos := strings.LastIndexByte(client.authUrl, '/')
  100. if pos > 0 {
  101. return client.authUrl[pos+1:]
  102. } else {
  103. return ""
  104. }
  105. }
  106. func (client *Client) NewAuthTokenCredential() TokenCredential {
  107. if client.AuthVersion() == "v3" {
  108. return &TokenCredentialV3{}
  109. }
  110. return &TokenCredentialV2{}
  111. }
  112. func getDefaultHeader(header http.Header, token string) http.Header {
  113. if len(token) > 0 {
  114. if header == nil {
  115. header = http.Header{}
  116. }
  117. if len(header.Get(AUTH_TOKEN)) == 0 {
  118. header.Add(AUTH_TOKEN, token)
  119. }
  120. }
  121. return header
  122. }
  123. func joinUrl(baseUrl, path string) string {
  124. base, version := SplitVersionedURL(baseUrl)
  125. if len(version) > 0 {
  126. if strings.HasPrefix(path, fmt.Sprintf("/%s/", version)) {
  127. baseUrl = base
  128. }
  129. }
  130. return fmt.Sprintf("%s%s", baseUrl, path)
  131. }
  132. func FixContext(ctx context.Context) context.Context {
  133. if ctx == nil {
  134. ctx = context.Background()
  135. }
  136. srvType := consts.GetServiceType()
  137. if len(srvType) > 0 && len(appctx.AppContextServiceName(ctx)) == 0 {
  138. ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_APPNAME, srvType)
  139. }
  140. return ctx
  141. }
  142. func (client *Client) rawRequest(ctx context.Context, endpoint string, token string, method httputils.THttpMethod, url string, header http.Header, body io.Reader) (*http.Response, error) {
  143. ctx = FixContext(ctx)
  144. return httputils.Request(client.httpconn, ctx, method, joinUrl(endpoint, url), getDefaultHeader(header, token), body, client.debug)
  145. }
  146. func (client *Client) jsonRequest(ctx context.Context, endpoint string, token string, method httputils.THttpMethod, url string, header http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
  147. ctx = FixContext(ctx)
  148. return httputils.JSONRequest(client.httpconn, ctx, method, joinUrl(endpoint, url), getDefaultHeader(header, token), body, client.debug)
  149. }
  150. func (client *Client) _authV3(domainName, uname, passwd, projectId, projectName, projectDomain, token string, aCtx SAuthContext) (TokenCredential, error) {
  151. input := SAuthenticationInputV3{}
  152. if len(uname) > 0 && len(passwd) > 0 { // Password authentication
  153. input.Auth.Identity.Methods = []string{api.AUTH_METHOD_PASSWORD}
  154. input.Auth.Identity.Password.User.Name = uname
  155. input.Auth.Identity.Password.User.Password = passwd
  156. if len(domainName) > 0 {
  157. input.Auth.Identity.Password.User.Domain.Name = domainName
  158. }
  159. // else {
  160. // input.Auth.Identity.Password.User.Domain.Name = api.DEFAULT_DOMAIN_ID
  161. //}
  162. } else if len(token) > 0 {
  163. input.Auth.Identity.Methods = []string{api.AUTH_METHOD_TOKEN}
  164. input.Auth.Identity.Token.Id = token
  165. }
  166. if len(projectId) > 0 {
  167. input.Auth.Scope.Project.Id = projectId
  168. }
  169. if len(projectName) > 0 {
  170. input.Auth.Scope.Project.Name = projectName
  171. if len(projectDomain) > 0 {
  172. input.Auth.Scope.Project.Domain.Name = projectDomain
  173. }
  174. // else {
  175. // input.Auth.Scope.Project.Domain.Id = api.DEFAULT_DOMAIN_ID
  176. // }
  177. }
  178. input.Auth.Context = aCtx
  179. return client._authV3Input(input)
  180. }
  181. func (client *Client) _authV3Input(input SAuthenticationInputV3) (TokenCredential, error) {
  182. hdr, rbody, err := client.jsonRequest(context.Background(), client.authUrl, "", "POST", "/auth/tokens", nil, jsonutils.Marshal(&input))
  183. if err != nil {
  184. return nil, err
  185. }
  186. tokenId := hdr.Get("X-Subject-Token")
  187. if len(tokenId) == 0 {
  188. return nil, errors.Wrap(httperrors.ErrInputParameter, "No X-Subject-Token in header")
  189. }
  190. ret, err := client.unmarshalV3Token(rbody, tokenId)
  191. return ret, err
  192. }
  193. func (client *Client) _authV2(uname, passwd, tenantId, tenantName, token string, aCtx SAuthContext) (TokenCredential, error) {
  194. input := SAuthenticationInputV2{}
  195. input.Auth.PasswordCredentials.Username = uname
  196. input.Auth.PasswordCredentials.Password = passwd
  197. if len(tenantName) > 0 {
  198. input.Auth.TenantName = tenantName
  199. }
  200. if len(tenantId) > 0 {
  201. input.Auth.TenantId = tenantId
  202. }
  203. if len(token) > 0 {
  204. input.Auth.Token.Id = token
  205. }
  206. input.Auth.Context = aCtx
  207. _, rbody, err := client.jsonRequest(context.Background(), client.authUrl, "", "POST", "/tokens", nil, jsonutils.Marshal(&input))
  208. if err != nil {
  209. return nil, err
  210. }
  211. return client.unmarshalV2Token(rbody)
  212. }
  213. func (client *Client) Authenticate(uname, passwd, domainName, tenantName, tenantDomain string) (TokenCredential, error) {
  214. return client.AuthenticateApi(uname, passwd, domainName, tenantName, tenantDomain)
  215. }
  216. func (client *Client) AuthenticateApi(uname, passwd, domainName, tenantName, tenantDomain string) (TokenCredential, error) {
  217. return client.AuthenticateWithSource(uname, passwd, domainName, tenantName, tenantDomain, AuthSourceAPI)
  218. }
  219. func (client *Client) AuthenticateWeb(uname, passwd, domainName, tenantName, tenantDomain string, cliIp string) (TokenCredential, error) {
  220. aCtx := SAuthContext{
  221. Source: AuthSourceWeb,
  222. Ip: cliIp,
  223. }
  224. return client.authenticateWithContext(uname, passwd, domainName, tenantName, tenantDomain, aCtx)
  225. }
  226. func (client *Client) AuthenticateOperator(uname, passwd, domainName, tenantName, tenantDomain string) (TokenCredential, error) {
  227. return client.AuthenticateWithSource(uname, passwd, domainName, tenantName, tenantDomain, AuthSourceOperator)
  228. }
  229. func (client *Client) AuthenticateWithSource(uname, passwd, domainName, tenantName, tenantDomain string, source string) (TokenCredential, error) {
  230. aCtx := SAuthContext{
  231. Source: source,
  232. }
  233. return client.authenticateWithContext(uname, passwd, domainName, tenantName, tenantDomain, aCtx)
  234. }
  235. func (client *Client) authenticateWithContext(uname, passwd, domainName, tenantName, tenantDomain string, aCtx SAuthContext) (TokenCredential, error) {
  236. if client.AuthVersion() == "v3" {
  237. return client._authV3(domainName, uname, passwd, "", tenantName, tenantDomain, "", aCtx)
  238. }
  239. return client._authV2(uname, passwd, "", tenantName, "", aCtx)
  240. }
  241. func (client *Client) unmarshalV3Token(rbody jsonutils.JSONObject, tokenId string) (cred TokenCredential, err error) {
  242. cred = &TokenCredentialV3{Id: tokenId}
  243. err = rbody.Unmarshal(cred)
  244. if err != nil {
  245. err = errors.Wrap(err, "Invalid response when unmarshal V3 Token")
  246. }
  247. cata := cred.GetServiceCatalog()
  248. if cata == nil || cata.Len() == 0 {
  249. log.Warningf("No service catalog avaiable")
  250. } else {
  251. client.SetServiceCatalog(cata)
  252. }
  253. return
  254. }
  255. func (client *Client) unmarshalV2Token(rbody jsonutils.JSONObject) (cred TokenCredential, err error) {
  256. access, err := rbody.Get("access")
  257. if err == nil {
  258. cred = &TokenCredentialV2{}
  259. err = access.Unmarshal(cred)
  260. if err != nil {
  261. err = errors.Wrap(err, "Invalid response when unmarshal V2 Token")
  262. }
  263. cata := cred.GetServiceCatalog()
  264. if cata == nil || cata.Len() == 0 {
  265. log.Warningf("No srvice catalog avaiable")
  266. } else {
  267. client.SetServiceCatalog(cata)
  268. }
  269. return
  270. }
  271. err = errors.Wrap(httperrors.ErrInvalidFormat, "Invalid response: no access object")
  272. return
  273. }
  274. func (client *Client) verifyV3(adminToken, token string) (TokenCredential, error) {
  275. header := http.Header{}
  276. header.Add(api.AUTH_TOKEN_HEADER, adminToken)
  277. header.Add(api.AUTH_SUBJECT_TOKEN_HEADER, token)
  278. _, rbody, err := client.jsonRequest(context.Background(), client.authUrl, "", "GET", "/auth/tokens", header, nil)
  279. if err != nil {
  280. return nil, err
  281. }
  282. return client.unmarshalV3Token(rbody, token)
  283. }
  284. func (client *Client) verifyV2(adminToken, token string) (TokenCredential, error) {
  285. header := http.Header{}
  286. header.Add(api.AUTH_TOKEN_HEADER, adminToken)
  287. verifyUrl := fmt.Sprintf("/tokens/%s", token)
  288. _, rbody, err := client.jsonRequest(context.Background(), client.authUrl, "", "GET", verifyUrl, header, nil)
  289. if err != nil {
  290. return nil, err
  291. }
  292. return client.unmarshalV2Token(rbody)
  293. }
  294. func (client *Client) Verify(adminToken, token string) (cred TokenCredential, err error) {
  295. if client.AuthVersion() == "v3" {
  296. return client.verifyV3(adminToken, token)
  297. }
  298. return client.verifyV2(adminToken, token)
  299. }
  300. func (client *Client) Invalidate(ctx context.Context, adminToken, token string) error {
  301. header := http.Header{}
  302. header.Add(api.AUTH_TOKEN_HEADER, adminToken)
  303. header.Add(api.AUTH_SUBJECT_TOKEN_HEADER, token)
  304. _, _, err := client.jsonRequest(ctx, client.authUrl, "", "DELETE", "/auth/tokens", header, nil)
  305. if err != nil {
  306. return errors.Wrap(err, "jsonRequest")
  307. }
  308. return nil
  309. }
  310. func (client *Client) FetchInvalidTokens(ctx context.Context, adminToken string) ([]string, error) {
  311. header := http.Header{}
  312. header.Add(api.AUTH_TOKEN_HEADER, adminToken)
  313. _, resp, err := client.jsonRequest(ctx, client.authUrl, "", "GET", "/auth/tokens/invalid", header, nil)
  314. if err != nil {
  315. return nil, errors.Wrap(err, "jsonRequest")
  316. }
  317. tokens := make([]string, 0)
  318. err = resp.Unmarshal(&tokens, "tokens")
  319. if err != nil {
  320. return nil, errors.Wrap(err, "Unmarshal")
  321. }
  322. return tokens, nil
  323. }
  324. func (client *Client) SetTenant(tenantId, tenantName, tenantDomain string, token TokenCredential) (TokenCredential, error) {
  325. return client.SetProject(tenantId, tenantName, tenantDomain, token)
  326. }
  327. func (client *Client) AuthenticateToken(token string, projName, projDomain string, source string) (TokenCredential, error) {
  328. aCtx := SAuthContext{
  329. Source: source,
  330. }
  331. if client.AuthVersion() == "v3" {
  332. return client._authV3("", "", "", "", projName, projDomain, token, aCtx)
  333. } else {
  334. return client._authV2("", "", "", projName, token, aCtx)
  335. }
  336. }
  337. func (client *Client) SetProject(tenantId, tenantName, tenantDomain string, token TokenCredential) (TokenCredential, error) {
  338. aCtx := SAuthContext{
  339. Source: token.GetLoginSource(),
  340. Ip: token.GetLoginIp(),
  341. }
  342. if client.AuthVersion() == "v3" {
  343. return client._authV3("", "", "", tenantId, tenantName, tenantDomain, token.GetTokenString(), aCtx)
  344. } else {
  345. return client._authV2("", "", "", tenantName, token.GetTokenString(), aCtx)
  346. }
  347. }
  348. func (client *Client) GetCommonEtcdEndpoint(token TokenCredential, region, interfaceType string) (*api.EndpointDetails, error) {
  349. if client.AuthVersion() != "v3" {
  350. return nil, errors.Errorf("current version %s not support get internal etcd endpoint", client.AuthVersion())
  351. }
  352. _, err := client.GetServiceCatalog().getServiceURL(apis.SERVICE_TYPE_ETCD, region, "", interfaceType)
  353. if err != nil {
  354. return nil, err
  355. }
  356. params := jsonutils.NewDict()
  357. params.Add(jsonutils.NewString(interfaceType), "interface")
  358. params.Add(jsonutils.JSONTrue, "enabled")
  359. params.Add(jsonutils.NewString(apis.SERVICE_TYPE_ETCD), "service")
  360. params.Add(jsonutils.JSONTrue, "details")
  361. params.Add(jsonutils.NewString(region), "region")
  362. epUrl := "/endpoints?" + params.QueryString()
  363. _, rbody, err := client.jsonRequest(context.Background(), client.authUrl, token.GetTokenString(), httputils.GET, epUrl, nil, nil)
  364. if err != nil {
  365. return nil, errors.Wrap(err, "get internal etcd endpoint")
  366. }
  367. rets, err := rbody.GetArray("endpoints")
  368. if err != nil {
  369. return nil, errors.Wrap(err, "get endpoints response")
  370. }
  371. if len(rets) == 0 {
  372. return nil, errors.Wrapf(httperrors.ErrNotFound, "not found service %s %s endpoint", apis.SERVICE_TYPE_ETCD, interfaceType)
  373. }
  374. if len(rets) > 1 {
  375. return nil, errors.Errorf("fond %d duplicate serivce %s %s endpoint", len(rets), apis.SERVICE_TYPE_ETCD, interfaceType)
  376. }
  377. endpoint := new(api.EndpointDetails)
  378. if err := rets[0].Unmarshal(endpoint); err != nil {
  379. return nil, errors.Wrap(err, "unmarshal endpoint")
  380. }
  381. return endpoint, nil
  382. }
  383. func (client *Client) GetCommonEtcdTLSConfig(endpoint *api.EndpointDetails) (*tls.Config, error) {
  384. if endpoint.CertId == "" {
  385. return nil, nil
  386. }
  387. caData := []byte(endpoint.CaCertificate)
  388. certData := []byte(endpoint.Certificate)
  389. keyData := []byte(endpoint.PrivateKey)
  390. return seclib2.InitTLSConfigByData(caData, certData, keyData)
  391. }
  392. func (client *Client) NewSession(ctx context.Context, region, zone, endpointType string, token TokenCredential) *ClientSession {
  393. cata := token.GetServiceCatalog()
  394. if client.GetServiceCatalog() == nil {
  395. if cata == nil || cata.Len() == 0 {
  396. log.Warningf("Missing service catalog in token")
  397. } else {
  398. client.SetServiceCatalog(cata)
  399. }
  400. }
  401. if ctx == nil {
  402. ctx = context.Background()
  403. }
  404. return &ClientSession{
  405. ctx: ctx,
  406. client: client,
  407. region: region,
  408. zone: zone,
  409. endpointType: endpointType,
  410. token: token,
  411. Header: http.Header{},
  412. customizeServiceUrl: map[string]string{},
  413. }
  414. }
  415. type SCheckPoliciesInput struct {
  416. UserId string
  417. ProjectId string
  418. LoginIp string
  419. }
  420. type SFetchMatchPoliciesOutput struct {
  421. Names map[rbacscope.TRbacScope][]string `json:"names"`
  422. Policies rbacutils.TPolicyGroup `json:"policies"`
  423. }
  424. func (o *SFetchMatchPoliciesOutput) Decode(object jsonutils.JSONObject) error {
  425. err := object.Unmarshal(&o.Names, "names")
  426. if err != nil {
  427. return errors.Wrap(err, "unmarshal names")
  428. }
  429. pData, err := object.Get("policies")
  430. if err != nil {
  431. return errors.Wrap(err, "Get policies")
  432. }
  433. o.Policies, err = rbacutils.DecodePolicyGroup(pData)
  434. if err != nil {
  435. return errors.Wrap(err, "DecodePolicyGroup")
  436. }
  437. return nil
  438. }
  439. func (o SFetchMatchPoliciesOutput) Encode() jsonutils.JSONObject {
  440. output := jsonutils.NewDict()
  441. output.Set("names", jsonutils.Marshal(o.Names))
  442. output.Set("policies", o.Policies.Encode())
  443. return output
  444. }
  445. func (client *Client) FetchMatchPolicies(ctx context.Context, token TokenCredential) (*SFetchMatchPoliciesOutput, error) {
  446. header := http.Header{}
  447. if token.GetTokenString() != "" {
  448. header.Add(api.AUTH_TOKEN_HEADER, token.GetTokenString())
  449. }
  450. _, rbody, err := client.jsonRequest(ctx, client.authUrl, "", "GET", "/auth/policies", header, nil)
  451. if err != nil {
  452. return nil, errors.Wrap(err, "jsonRequest")
  453. }
  454. output := &SFetchMatchPoliciesOutput{}
  455. err = output.Decode(rbody)
  456. if err != nil {
  457. return nil, errors.Wrap(err, "SFetchMatchPoliciesOutput.Decode")
  458. }
  459. return output, nil
  460. }
  461. func (client *Client) CheckMatchPolicies(ctx context.Context, adminToken TokenCredential, input SCheckPoliciesInput) (*SFetchMatchPoliciesOutput, error) {
  462. _, rbody, err := client.jsonRequest(ctx, client.authUrl, adminToken.GetTokenString(), "POST", "/auth/policies", nil, jsonutils.Marshal(input))
  463. if err != nil {
  464. return nil, errors.Wrap(err, "jsonRequest")
  465. }
  466. output := &SFetchMatchPoliciesOutput{}
  467. err = output.Decode(rbody)
  468. if err != nil {
  469. return nil, errors.Wrap(err, "SFetchMatchPoliciesOutput.Decode")
  470. }
  471. return output, nil
  472. }