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  }