...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/writer/fs.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/writer

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package writer
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path"
    25  
    26  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/generator"
    27  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/writer/atomic"
    28  
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  )
    31  
    32  // fsCertWriter provisions the certificate by reading and writing to the filesystem.
    33  type fsCertWriter struct {
    34  	// dnsName is the DNS name that the certificate is for.
    35  	dnsName string
    36  
    37  	*FSCertWriterOptions
    38  }
    39  
    40  // FSCertWriterOptions are options for constructing a FSCertWriter.
    41  type FSCertWriterOptions struct {
    42  	// certGenerator generates the certificates.
    43  	CertGenerator generator.CertGenerator
    44  	// path is the directory that the certificate and private key and CA certificate will be written.
    45  	Path string
    46  }
    47  
    48  var _ CertWriter = &fsCertWriter{}
    49  
    50  func (ops *FSCertWriterOptions) setDefaults() {
    51  	if ops.CertGenerator == nil {
    52  		ops.CertGenerator = &generator.SelfSignedCertGenerator{}
    53  	}
    54  }
    55  
    56  func (ops *FSCertWriterOptions) validate() error {
    57  	if len(ops.Path) == 0 {
    58  		return errors.New("path must be set in FSCertWriterOptions")
    59  	}
    60  	return nil
    61  }
    62  
    63  // NewFSCertWriter constructs a CertWriter that persists the certificate on filesystem.
    64  func NewFSCertWriter(ops FSCertWriterOptions) (CertWriter, error) {
    65  	ops.setDefaults()
    66  	err := ops.validate()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return &fsCertWriter{
    71  		FSCertWriterOptions: &ops,
    72  	}, nil
    73  }
    74  
    75  // EnsureCert provisions certificates for a webhookClientConfig by writing the certificates in the filesystem.
    76  func (f *fsCertWriter) EnsureCert(dnsName string) (*generator.Artifacts, bool, error) {
    77  	// create or refresh cert and write it to fs
    78  	f.dnsName = dnsName
    79  	return handleCommon(f.dnsName, f)
    80  }
    81  
    82  func (f *fsCertWriter) write() (*generator.Artifacts, error) {
    83  	return f.doWrite()
    84  }
    85  
    86  func (f *fsCertWriter) overwrite() (*generator.Artifacts, error) {
    87  	return f.doWrite()
    88  }
    89  
    90  func (f *fsCertWriter) doWrite() (*generator.Artifacts, error) {
    91  	certs, err := f.CertGenerator.Generate(f.dnsName)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// AtomicWriter's algorithm only manages files using symbolic link.
    97  	// If a file is not a symbolic link, will ignore the update for it.
    98  	// We want to cleanup for AtomicWriter by removing old files that are not symbolic links.
    99  	err = prepareToWrite(f.Path)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	aw, err := atomic.NewAtomicWriter(f.Path, log.WithName("atomic-writer").
   105  		WithValues("task", "processing webhook"))
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	err = aw.Write(certToProjectionMap(certs))
   110  	return certs, err
   111  }
   112  
   113  // prepareToWrite ensures it directory is compatible with the atomic.Writer library.
   114  func prepareToWrite(dir string) error {
   115  	_, err := os.Stat(dir)
   116  	switch {
   117  	case os.IsNotExist(err):
   118  		log.Info("cert directory doesn't exist, creating", "directory", dir)
   119  		// TODO: figure out if we can reduce the permission. (Now it's 0777)
   120  		err = os.MkdirAll(dir, 0777)
   121  		if err != nil {
   122  			return fmt.Errorf("can't create dir: %v", dir)
   123  		}
   124  	case err != nil:
   125  		return err
   126  	}
   127  
   128  	filenames := []string{CAKeyName, CACertName, ServerCertName, ServerKeyName}
   129  	for _, f := range filenames {
   130  		abspath := path.Join(dir, f)
   131  		_, err := os.Stat(abspath)
   132  		if os.IsNotExist(err) {
   133  			continue
   134  		} else if err != nil {
   135  			log.Error(err, "unable to stat file", "file", abspath)
   136  		}
   137  		_, err = os.Readlink(abspath)
   138  		// if it's not a symbolic link
   139  		if err != nil {
   140  			err = os.Remove(abspath)
   141  			if err != nil {
   142  				log.Error(err, "unable to remove old file", "file", abspath)
   143  			}
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func (f *fsCertWriter) read() (*generator.Artifacts, error) {
   150  	if err := ensureExist(f.Path); err != nil {
   151  		return nil, err
   152  	}
   153  	caKeyBytes, err := ioutil.ReadFile(path.Join(f.Path, CAKeyName))
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	caCertBytes, err := ioutil.ReadFile(path.Join(f.Path, CACertName))
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	certBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerCertName))
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	keyBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerKeyName))
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	return &generator.Artifacts{
   170  		CAKey:  caKeyBytes,
   171  		CACert: caCertBytes,
   172  		Cert:   certBytes,
   173  		Key:    keyBytes,
   174  	}, nil
   175  }
   176  
   177  func ensureExist(dir string) error {
   178  	filenames := []string{CAKeyName, CACertName, ServerCertName, ServerKeyName}
   179  	for _, filename := range filenames {
   180  		_, err := os.Stat(path.Join(dir, filename))
   181  		switch {
   182  		case err == nil:
   183  			continue
   184  		case os.IsNotExist(err):
   185  			return notFoundError{err}
   186  		default:
   187  			return err
   188  		}
   189  	}
   190  	return nil
   191  }
   192  
   193  func certToProjectionMap(cert *generator.Artifacts) map[string]atomic.FileProjection {
   194  	// TODO: figure out if we can reduce the permission. (Now it's 0666)
   195  	return map[string]atomic.FileProjection{
   196  		CAKeyName: {
   197  			Data: cert.CAKey,
   198  			Mode: 0666,
   199  		},
   200  		CACertName: {
   201  			Data: cert.CACert,
   202  			Mode: 0666,
   203  		},
   204  		ServerCertName: {
   205  			Data: cert.Cert,
   206  			Mode: 0666,
   207  		},
   208  		ServerKeyName: {
   209  			Data: cert.Key,
   210  			Mode: 0666,
   211  		},
   212  	}
   213  }
   214  
   215  func (f *fsCertWriter) Inject(objs ...client.Object) error {
   216  	return nil
   217  }
   218  

View as plain text