keypairs.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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. "strings"
  18. "golang.org/x/crypto/ssh"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/pkg/errors"
  21. "yunion.io/x/pkg/gotypes"
  22. "yunion.io/x/pkg/util/rbacscope"
  23. "yunion.io/x/sqlchemy"
  24. "yunion.io/x/onecloud/pkg/apis"
  25. api "yunion.io/x/onecloud/pkg/apis/compute"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  27. "yunion.io/x/onecloud/pkg/httperrors"
  28. "yunion.io/x/onecloud/pkg/mcclient"
  29. "yunion.io/x/onecloud/pkg/util/logclient"
  30. "yunion.io/x/onecloud/pkg/util/seclib2"
  31. "yunion.io/x/onecloud/pkg/util/stringutils2"
  32. )
  33. type SKeypairManager struct {
  34. db.SUserResourceBaseManager
  35. db.SSharableBaseResourceManager
  36. }
  37. var KeypairManager *SKeypairManager
  38. func init() {
  39. KeypairManager = &SKeypairManager{
  40. SUserResourceBaseManager: db.NewUserResourceBaseManager(
  41. SKeypair{},
  42. "keypairs_tbl",
  43. "keypair",
  44. "keypairs",
  45. ),
  46. }
  47. KeypairManager.SetVirtualObject(KeypairManager)
  48. }
  49. type SKeypair struct {
  50. db.SUserResourceBase
  51. db.SSharableBaseResource
  52. // 加密类型
  53. // example: RSA
  54. Scheme string `width:"12" charset:"ascii" nullable:"true" list:"user" create:"required"`
  55. // 指纹信息
  56. // example: 1d:3a:83:4a:a1:f3:75:97:ec:d1:ef:f8:3f:a7:5d:9e
  57. Fingerprint string `width:"48" charset:"ascii" nullable:"false" list:"user" create:"required"`
  58. // 私钥
  59. PrivateKey string `width:"2048" charset:"ascii" nullable:"true" create:"optional"`
  60. // 公钥
  61. PublicKey string `width:"1024" charset:"ascii" nullable:"false" list:"user" create:"required"`
  62. }
  63. func (manager *SKeypairManager) GetISharableVirtualModelManager() db.ISharableVirtualModelManager {
  64. return manager.GetVirtualObject().(db.ISharableVirtualModelManager)
  65. }
  66. func (manager *SKeypairManager) GetIVirtualModelManager() db.IVirtualModelManager {
  67. return manager.GetVirtualObject().(db.IVirtualModelManager)
  68. }
  69. // 列出ssh密钥对
  70. func (manager *SKeypairManager) ListItemFilter(
  71. ctx context.Context,
  72. q *sqlchemy.SQuery,
  73. userCred mcclient.TokenCredential,
  74. query api.KeypairListInput,
  75. ) (*sqlchemy.SQuery, error) {
  76. q, err := manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput)
  77. if err != nil {
  78. return nil, err
  79. }
  80. if ((query.Admin != nil && *query.Admin) || query.Scope == string(rbacscope.ScopeSystem)) && db.IsAdminAllowList(userCred, manager).Result.IsAllow() {
  81. user := query.UserId
  82. if len(user) > 0 {
  83. uc, _ := db.UserCacheManager.FetchUserByIdOrName(ctx, user)
  84. if uc == nil {
  85. return nil, httperrors.NewUserNotFoundError("user %s not found", user)
  86. }
  87. q = q.Equals("owner_id", uc.Id)
  88. }
  89. } else {
  90. q, err = manager.SSharableBaseResourceManager.ListItemFilter(ctx, q, userCred, query.SharableResourceBaseListInput)
  91. if err != nil {
  92. return nil, errors.Wrap(err, "SSharableBaseResourceManager.ListItemFilter")
  93. }
  94. }
  95. if len(query.Scheme) > 0 {
  96. q = q.In("scheme", query.Scheme)
  97. }
  98. if len(query.Fingerprint) > 0 {
  99. q = q.In("fingerprint", query.Fingerprint)
  100. }
  101. return q, nil
  102. }
  103. func (manager *SKeypairManager) OrderByExtraFields(
  104. ctx context.Context,
  105. q *sqlchemy.SQuery,
  106. userCred mcclient.TokenCredential,
  107. query api.KeypairListInput,
  108. ) (*sqlchemy.SQuery, error) {
  109. var err error
  110. q, err = manager.SUserResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.UserResourceListInput)
  111. if err != nil {
  112. return nil, errors.Wrap(err, "SUserResourceBaseManager.OrderByExtraFields")
  113. }
  114. return q, nil
  115. }
  116. func (manager *SKeypairManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  117. var err error
  118. q, err = manager.SUserResourceBaseManager.QueryDistinctExtraField(q, field)
  119. if err == nil {
  120. return q, nil
  121. }
  122. return q, httperrors.ErrNotFound
  123. }
  124. func (km *SKeypairManager) query(manager db.IModelManager, field string, keyIds []string, filter func(*sqlchemy.SQuery) *sqlchemy.SQuery) *sqlchemy.SSubQuery {
  125. q := manager.Query()
  126. if filter != nil {
  127. q = filter(q)
  128. }
  129. sq := q.SubQuery()
  130. return sq.Query(
  131. sq.Field("keypair_id"),
  132. sqlchemy.COUNT(field),
  133. ).In("keypair_id", keyIds).GroupBy(sq.Field("keypair_id")).SubQuery()
  134. }
  135. type SKeypairUsageCount struct {
  136. Id string
  137. LinkedGuestCount int
  138. }
  139. func (km *SKeypairManager) TotalResourceCount(keyIds []string) (map[string]SKeypairUsageCount, error) {
  140. ret := map[string]SKeypairUsageCount{}
  141. guestSQ := km.query(GuestManager, "guest_cnt", keyIds, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  142. return q.IsNotEmpty("keypair_id")
  143. })
  144. keypairs := km.Query().SubQuery()
  145. keypairsQ := keypairs.Query(
  146. sqlchemy.SUM("linked_guest_count", guestSQ.Field("guest_cnt")),
  147. )
  148. keypairsQ.AppendField(keypairsQ.Field("id"))
  149. keypairsQ = keypairsQ.LeftJoin(guestSQ, sqlchemy.Equals(keypairsQ.Field("id"), guestSQ.Field("keypair_id")))
  150. keypairsQ = keypairsQ.Filter(sqlchemy.In(keypairsQ.Field("id"), keyIds)).GroupBy(keypairsQ.Field("id"))
  151. counts := []SKeypairUsageCount{}
  152. err := keypairsQ.All(&counts)
  153. if err != nil {
  154. return nil, errors.Wrapf(err, "keyparisQ.All")
  155. }
  156. for i := range counts {
  157. ret[counts[i].Id] = counts[i]
  158. }
  159. return ret, nil
  160. }
  161. func (manager *SKeypairManager) FetchCustomizeColumns(
  162. ctx context.Context,
  163. userCred mcclient.TokenCredential,
  164. query jsonutils.JSONObject,
  165. objs []interface{},
  166. fields stringutils2.SSortedStrings,
  167. isList bool,
  168. ) []api.KeypairDetails {
  169. rows := make([]api.KeypairDetails, len(objs))
  170. userRows := manager.SUserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  171. shareRows := manager.SSharableBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  172. keyIds := make([]string, len(objs))
  173. for i := range rows {
  174. keypair := objs[i].(*SKeypair)
  175. rows[i] = api.KeypairDetails{
  176. UserResourceDetails: userRows[i],
  177. SharableResourceBaseInfo: shareRows[i],
  178. PrivateKeyLen: len(keypair.PrivateKey),
  179. }
  180. keyIds[i] = keypair.Id
  181. }
  182. usages, err := manager.TotalResourceCount(keyIds)
  183. if err != nil {
  184. return rows
  185. }
  186. for i := range rows {
  187. if cnt, ok := usages[keyIds[i]]; ok {
  188. rows[i].LinkedGuestCount = cnt.LinkedGuestCount
  189. }
  190. }
  191. return rows
  192. }
  193. func (self *SKeypair) GetLinkedGuestsCount() (int, error) {
  194. return GuestManager.Query().Equals("keypair_id", self.Id).CountWithError()
  195. }
  196. func (manager *SKeypairManager) ValidateCreateData(
  197. ctx context.Context,
  198. userCred mcclient.TokenCredential,
  199. ownerId mcclient.IIdentityProvider,
  200. query jsonutils.JSONObject,
  201. input *api.KeypairCreateInput,
  202. ) (*api.KeypairCreateInput, error) {
  203. input.PublicKey = strings.TrimSpace(input.PublicKey)
  204. if len(input.PublicKey) == 0 {
  205. if len(input.Scheme) == 0 {
  206. input.Scheme = api.KEYPAIRE_SCHEME_RSA
  207. }
  208. var err error
  209. switch input.Scheme {
  210. case api.KEYPAIRE_SCHEME_RSA:
  211. input.PrivateKey, input.PublicKey, err = seclib2.GenerateRSASSHKeypair()
  212. case api.KEYPAIRE_SCHEME_DSA:
  213. input.PrivateKey, input.PublicKey, err = seclib2.GenerateDSASSHKeypair()
  214. case api.KEYPAIRE_SCHEME_ECDSA:
  215. input.PrivateKey, input.PublicKey, err = seclib2.GenerateECDSASHAP521SSHKeypair()
  216. case api.KEYPAIRE_SCHEME_ED25519:
  217. input.PrivateKey, input.PublicKey, err = seclib2.GenerateED25519SSHKeypair()
  218. default:
  219. return nil, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme)
  220. }
  221. if err != nil {
  222. return nil, httperrors.NewGeneralError(errors.Wrapf(err, "Generate%sSSHKeypair", input.Scheme))
  223. }
  224. }
  225. pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(input.PublicKey))
  226. if err != nil {
  227. return input, httperrors.NewInputParameterError("invalid public error: %v", err)
  228. }
  229. input.Scheme = seclib2.GetPublicKeyScheme(pubKey)
  230. input.Fingerprint = ssh.FingerprintLegacyMD5(pubKey)
  231. input.UserResourceCreateInput, err = manager.SUserResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.UserResourceCreateInput)
  232. if err != nil {
  233. return input, err
  234. }
  235. return input, nil
  236. }
  237. func (self *SKeypair) ValidateDeleteCondition(ctx context.Context, info *api.KeypairDetails) error {
  238. if gotypes.IsNil(info) {
  239. info = &api.KeypairDetails{}
  240. var err error
  241. info.LinkedGuestCount, err = self.GetLinkedGuestsCount()
  242. if err != nil {
  243. return httperrors.NewInternalServerError("GetLinkedGuestsCount failed %s", err)
  244. }
  245. }
  246. if info.LinkedGuestCount > 0 {
  247. return httperrors.NewNotEmptyError("Cannot delete keypair used by servers")
  248. }
  249. return self.SStandaloneResourceBase.ValidateDeleteCondition(ctx, nil)
  250. }
  251. func (manager *SKeypairManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  252. return db.SharableManagerFilterByOwner(ctx, manager.GetISharableVirtualModelManager(), q, userCred, owner, scope)
  253. }
  254. func (keypair *SKeypair) GetDetailsPrivatekey(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  255. retval := jsonutils.NewDict()
  256. if len(keypair.PrivateKey) > 0 {
  257. retval.Add(jsonutils.NewString(keypair.PrivateKey), "private_key")
  258. retval.Add(jsonutils.NewString(keypair.Name), "name")
  259. retval.Add(jsonutils.NewString(keypair.Scheme), "scheme")
  260. _, err := db.Update(keypair, func() error {
  261. keypair.PrivateKey = ""
  262. return nil
  263. })
  264. if err != nil {
  265. return nil, err
  266. }
  267. db.OpsLog.LogEvent(keypair, db.ACT_FETCH, nil, userCred)
  268. logclient.AddActionLogWithContext(ctx, keypair, logclient.ACT_FETCH, nil, userCred, true)
  269. }
  270. return retval, nil
  271. }
  272. func (self *SKeypair) GetOwnerId() mcclient.IIdentityProvider {
  273. owner := &db.SOwnerId{UserId: self.OwnerId}
  274. obj, err := db.UserCacheManager.FetchById(self.OwnerId)
  275. if err != nil {
  276. return owner
  277. }
  278. user := obj.(*db.SUser)
  279. owner.DomainId = user.DomainId
  280. return owner
  281. }
  282. func (self *SKeypair) GetProjectDomainId() string {
  283. obj, err := db.UserCacheManager.FetchById(self.OwnerId)
  284. if err != nil {
  285. return ""
  286. }
  287. user := obj.(*db.SUser)
  288. return user.DomainId
  289. }
  290. func (self *SKeypair) GetRequiredSharedDomainIds() []string {
  291. obj, err := db.UserCacheManager.FetchById(self.OwnerId)
  292. if err != nil {
  293. return []string{}
  294. }
  295. user := obj.(*db.SUser)
  296. return []string{user.DomainId}
  297. }
  298. func (self *SKeypair) GetSharableTargetDomainIds() []string {
  299. return []string{}
  300. }
  301. func (self *SKeypair) GetChangeOwnerRequiredDomainIds() []string {
  302. domainId := self.GetProjectDomainId()
  303. if len(domainId) > 0 {
  304. return []string{domainId}
  305. }
  306. return []string{}
  307. }
  308. func (self *SKeypair) GetChangeOwnerCandidateDomainIds() []string {
  309. domains := []db.STenant{}
  310. db.TenantCacheManager.GetDomainQuery().All(&domains)
  311. ret := []string{}
  312. for i := range domains {
  313. ret = append(ret, domains[i].Id)
  314. }
  315. return ret
  316. }
  317. func (self *SKeypair) GetSharedDomains() []string {
  318. return db.SharableGetSharedProjects(self, db.SharedTargetDomain)
  319. }
  320. func (keypair *SKeypair) GetISharableModel() db.ISharableBaseModel {
  321. return keypair.GetVirtualObject().(db.ISharableBaseModel)
  322. }
  323. func (keypair *SKeypair) GetDetailsChangeOwnerCandidateDomains(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (apis.ChangeOwnerCandidateDomainsOutput, error) {
  324. return db.IOwnerResourceBaseModelGetChangeOwnerCandidateDomains(keypair)
  325. }
  326. func (self *SKeypair) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPublicProjectInput) (jsonutils.JSONObject, error) {
  327. err := db.SharablePerformPublic(self.GetISharableModel(), ctx, userCred, input)
  328. if err != nil {
  329. return nil, errors.Wrap(err, "SharablePerformPublic")
  330. }
  331. return nil, nil
  332. }
  333. func (self *SKeypair) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformPrivateInput) (jsonutils.JSONObject, error) {
  334. err := db.SharablePerformPrivate(self.GetISharableModel(), ctx, userCred)
  335. if err != nil {
  336. return nil, errors.Wrap(err, "SharablePerformPrivate")
  337. }
  338. return nil, nil
  339. }