github.com/sacloud/libsacloud/v2@v2.32.3/helper/service/certificateauthority/builder.go (about) 1 // Copyright 2016-2022 The Libsacloud Authors 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 15 package certificateauthority 16 17 import ( 18 "context" 19 "time" 20 21 "github.com/sacloud/libsacloud/v2/helper/wait" 22 23 "github.com/sacloud/libsacloud/v2/sacloud" 24 "github.com/sacloud/libsacloud/v2/sacloud/types" 25 ) 26 27 // Builder マネージドPKI(CA)のビルダー 28 type Builder struct { 29 ID types.ID // 新規登録時は空にする 30 31 Name string 32 Description string 33 Tags types.Tags 34 IconID types.ID 35 36 Country string 37 Organization string 38 OrganizationUnit []string 39 CommonName string 40 NotAfter time.Time 41 42 Clients []*ClientCert // Note: ここに指定しなかったものはRevokeされる 43 Servers []*ServerCert // Note: ここに指定しなかったものはRevokeされる 44 45 Client sacloud.CertificateAuthorityAPI 46 47 PollingTimeout time.Duration // 証明書発行待ちのタイムアウト 48 PollingInterval time.Duration // 証明書発行待ちのポーリング間隔 49 } 50 51 // ClientCert クライアント証明書のリクエストパラメータ 52 type ClientCert struct { 53 ID string // 新規登録時は空にする 54 55 Country string 56 Organization string 57 OrganizationUnit []string 58 CommonName string 59 NotAfter time.Time 60 IssuanceMethod types.ECertificateAuthorityIssuanceMethod 61 EMail string 62 CertificateSigningRequest string 63 PublicKey string 64 65 Hold bool // 一時停止する時はTrue 66 } 67 68 // ServerCert サーバ証明書のリクエストパラメータ 69 type ServerCert struct { 70 ID string // 新規作成時は空にする 71 72 Country string 73 Organization string 74 OrganizationUnit []string 75 CommonName string 76 NotAfter time.Time 77 SANs []string 78 CertificateSigningRequest string 79 PublicKey string 80 81 Hold bool // 一時停止する時はTrue 82 } 83 84 // CertificateAuthority sacloud/CertificateAuthorityのラッパー 85 // 86 // CA/クライアント/サーバの詳細情報を保持する 87 type CertificateAuthority struct { 88 sacloud.CertificateAuthority 89 90 Detail *sacloud.CertificateAuthorityDetail 91 Clients []*sacloud.CertificateAuthorityClient 92 Servers []*sacloud.CertificateAuthorityServer 93 } 94 95 func (b *Builder) Build(ctx context.Context) (*CertificateAuthority, error) { 96 if b.ID.IsEmpty() { 97 return b.create(ctx) 98 } 99 return b.update(ctx) 100 } 101 102 func (b *Builder) create(ctx context.Context) (*CertificateAuthority, error) { 103 created, err := b.Client.Create(ctx, &sacloud.CertificateAuthorityCreateRequest{ 104 Name: b.Name, 105 Description: b.Description, 106 Tags: b.Tags, 107 IconID: b.IconID, 108 Country: b.Country, 109 Organization: b.Organization, 110 OrganizationUnit: b.OrganizationUnit, 111 CommonName: b.CommonName, 112 NotAfter: b.NotAfter, 113 }) 114 if err != nil { 115 return nil, err 116 } 117 err = b.wait(ctx, func() (bool, error) { 118 detail, err := b.Client.Detail(ctx, created.ID) 119 if err != nil { 120 return false, err 121 } 122 return detail.CertificateData != nil, nil // CA自体の証明書が発行されるまでCertificateDataは空のことがある 123 }) 124 if err != nil { 125 return nil, err 126 } 127 128 if err := b.reconcileClients(ctx, created.ID); err != nil { 129 return nil, err 130 } 131 if err := b.reconcileServers(ctx, created.ID); err != nil { 132 return nil, err 133 } 134 135 return read(ctx, b.Client, created.ID) 136 } 137 138 func (b *Builder) update(ctx context.Context) (*CertificateAuthority, error) { 139 updated, err := b.Client.Update(ctx, b.ID, &sacloud.CertificateAuthorityUpdateRequest{ 140 Name: b.Name, 141 Description: b.Description, 142 Tags: b.Tags, 143 IconID: b.IconID, 144 }) 145 if err != nil { 146 return nil, err 147 } 148 149 if err := b.reconcileClients(ctx, updated.ID); err != nil { 150 return nil, err 151 } 152 if err := b.reconcileServers(ctx, updated.ID); err != nil { 153 return nil, err 154 } 155 156 return read(ctx, b.Client, updated.ID) 157 } 158 159 func (b *Builder) reconcileClients(ctx context.Context, id types.ID) error { 160 currentCerts, err := b.Client.ListClients(ctx, id) 161 if err != nil { 162 return err 163 } 164 165 if currentCerts != nil { 166 // delete 167 for _, target := range b.deletedClients(currentCerts.CertificateAuthority) { 168 switch target.IssueState { 169 case "available": 170 if err := b.Client.RevokeClient(ctx, id, target.ID); err != nil { 171 return err 172 } 173 case "approved": 174 if err := b.Client.DenyClient(ctx, id, target.ID); err != nil { 175 return err 176 } 177 } 178 } 179 180 // update 181 for _, target := range b.updatedClients(currentCerts.CertificateAuthority) { 182 if target.Hold { 183 if err := b.Client.HoldClient(ctx, id, target.ID); err != nil { 184 return err 185 } 186 } else { 187 if err := b.Client.ResumeClient(ctx, id, target.ID); err != nil { 188 return err 189 } 190 } 191 } 192 } 193 // create 194 for _, target := range b.createdClients() { 195 if err := b.addClient(ctx, id, target); err != nil { 196 return err 197 } 198 } 199 200 return nil 201 } 202 203 func (b *Builder) deletedClients(currentClients []*sacloud.CertificateAuthorityClient) []*sacloud.CertificateAuthorityClient { 204 var results []*sacloud.CertificateAuthorityClient 205 for _, current := range currentClients { 206 exists := false 207 for _, desired := range b.Clients { 208 if current.ID == "" || current.ID == desired.ID { 209 exists = true 210 break 211 } 212 } 213 if !exists { 214 results = append(results, current) 215 } 216 } 217 return results 218 } 219 220 func (b *Builder) updatedClients(currentClients []*sacloud.CertificateAuthorityClient) []*ClientCert { 221 var results []*ClientCert 222 for _, current := range currentClients { 223 for _, desired := range b.Clients { 224 if current.ID == desired.ID { 225 if (desired.Hold && current.IssueState == "available") || (!desired.Hold && current.IssueState == "hold") { 226 results = append(results, desired) 227 } 228 break 229 } 230 } 231 } 232 return results 233 } 234 235 func (b *Builder) createdClients() []*ClientCert { 236 var results []*ClientCert 237 for _, created := range b.Clients { 238 if created.ID == "" { 239 results = append(results, created) 240 } 241 } 242 return results 243 } 244 245 func (b *Builder) addClient(ctx context.Context, id types.ID, cc *ClientCert) error { 246 cert, err := b.Client.AddClient(ctx, id, &sacloud.CertificateAuthorityAddClientParam{ 247 Country: cc.Country, 248 Organization: cc.Organization, 249 OrganizationUnit: cc.OrganizationUnit, 250 CommonName: cc.CommonName, 251 NotAfter: cc.NotAfter, 252 IssuanceMethod: cc.IssuanceMethod, 253 EMail: cc.EMail, 254 CertificateSigningRequest: cc.CertificateSigningRequest, 255 PublicKey: cc.PublicKey, 256 }) 257 if err != nil { 258 return err 259 } 260 cc.ID = cert.ID // 発行されたIDをBuilderに書き戻しておく 261 262 // 証明書発行待ち、URLまたはEMailの場合は待たなくても良い(任意のURLにアクセスしWASMで.p12を発行するため) 263 switch cc.IssuanceMethod { 264 case types.CertificateAuthorityIssuanceMethods.CSR, types.CertificateAuthorityIssuanceMethods.PublicKey: 265 err = b.wait(ctx, func() (bool, error) { 266 c, err := b.Client.ReadClient(ctx, id, cert.ID) 267 if err != nil { 268 return false, err 269 } 270 return c.CertificateData != nil, nil 271 }) 272 if err != nil { 273 return err 274 } 275 } 276 277 if cc.Hold { 278 if err := b.Client.HoldClient(ctx, id, cert.ID); err != nil { 279 return err 280 } 281 } 282 return nil 283 } 284 285 func (b *Builder) reconcileServers(ctx context.Context, id types.ID) error { 286 currentCerts, err := b.Client.ListServers(ctx, id) 287 if err != nil { 288 return err 289 } 290 291 if currentCerts != nil { 292 // delete 293 for _, target := range b.deletedServers(currentCerts.CertificateAuthority) { 294 switch target.IssueState { 295 case "available": 296 if err := b.Client.RevokeServer(ctx, id, target.ID); err != nil { 297 return err 298 } 299 } 300 } 301 302 // update 303 for _, target := range b.updatedServers(currentCerts.CertificateAuthority) { 304 if target.Hold { 305 if err := b.Client.HoldServer(ctx, id, target.ID); err != nil { 306 return err 307 } 308 } else { 309 if err := b.Client.ResumeServer(ctx, id, target.ID); err != nil { 310 return err 311 } 312 } 313 } 314 } 315 // create 316 for _, target := range b.createdServers() { 317 if err := b.addServer(ctx, id, target); err != nil { 318 return err 319 } 320 } 321 322 return nil 323 } 324 325 func (b *Builder) deletedServers(currentServers []*sacloud.CertificateAuthorityServer) []*sacloud.CertificateAuthorityServer { 326 var results []*sacloud.CertificateAuthorityServer 327 for _, current := range currentServers { 328 exists := false 329 for _, desired := range b.Servers { 330 if current.ID == "" || current.ID == desired.ID { 331 exists = true 332 break 333 } 334 } 335 if !exists { 336 results = append(results, current) 337 } 338 } 339 return results 340 } 341 342 func (b *Builder) updatedServers(currentServers []*sacloud.CertificateAuthorityServer) []*ServerCert { 343 var results []*ServerCert 344 for _, current := range currentServers { 345 for _, desired := range b.Servers { 346 if current.ID == desired.ID { 347 if (desired.Hold && current.IssueState == "available") || (!desired.Hold && current.IssueState == "hold") { 348 results = append(results, desired) 349 } 350 break 351 } 352 } 353 } 354 return results 355 } 356 357 func (b *Builder) createdServers() []*ServerCert { 358 var results []*ServerCert 359 for _, created := range b.Servers { 360 if created.ID == "" { 361 results = append(results, created) 362 } 363 } 364 return results 365 } 366 367 func (b *Builder) addServer(ctx context.Context, id types.ID, sc *ServerCert) error { 368 cert, err := b.Client.AddServer(ctx, id, &sacloud.CertificateAuthorityAddServerParam{ 369 Country: sc.Country, 370 Organization: sc.Organization, 371 OrganizationUnit: sc.OrganizationUnit, 372 CommonName: sc.CommonName, 373 NotAfter: sc.NotAfter, 374 SANs: sc.SANs, 375 CertificateSigningRequest: sc.CertificateSigningRequest, 376 PublicKey: sc.PublicKey, 377 }) 378 if err != nil { 379 return err 380 } 381 sc.ID = cert.ID // 発行されたIDをBuilderに書き戻しておく 382 383 err = b.wait(ctx, func() (bool, error) { 384 c, err := b.Client.ReadServer(ctx, id, cert.ID) 385 if err != nil { 386 return false, err 387 } 388 return c.CertificateData != nil, nil 389 }) 390 if err != nil { 391 return err 392 } 393 394 if sc.Hold { 395 if err := b.Client.HoldServer(ctx, id, cert.ID); err != nil { 396 return err 397 } 398 } 399 return nil 400 } 401 402 func (b *Builder) wait(ctx context.Context, readStateFunc wait.ReadStateFunc) error { 403 timeout := b.PollingTimeout 404 if timeout == time.Duration(0) { 405 timeout = time.Minute // デフォルト: 5分 406 } 407 interval := b.PollingInterval 408 if interval == time.Duration(0) { 409 interval = 5 * time.Second 410 } 411 412 waiter := &wait.SimpleStateWaiter{ 413 ReadStateFunc: readStateFunc, 414 Timeout: timeout, 415 PollingInterval: interval, 416 } 417 418 _, err := waiter.WaitForState(ctx) 419 return err 420 }