232 lines
5.7 KiB
Go
232 lines
5.7 KiB
Go
/*
|
|
Copyright 2020 The Kruise Authors.
|
|
Copyright 2018 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package writer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
|
|
"k8s.io/klog/v2"
|
|
|
|
"github.com/openkruise/rollouts/pkg/webhook/util/generator"
|
|
"github.com/openkruise/rollouts/pkg/webhook/util/writer/atomic"
|
|
)
|
|
|
|
const (
|
|
FsCertWriter = "fs"
|
|
)
|
|
|
|
// fsCertWriter provisions the certificate by reading and writing to the filesystem.
|
|
type fsCertWriter struct {
|
|
// dnsName is the DNS name that the certificate is for.
|
|
dnsName string
|
|
|
|
*FSCertWriterOptions
|
|
}
|
|
|
|
// FSCertWriterOptions are options for constructing a FSCertWriter.
|
|
type FSCertWriterOptions struct {
|
|
// certGenerator generates the certificates.
|
|
CertGenerator generator.CertGenerator
|
|
// path is the directory that the certificate and private key and CA certificate will be written.
|
|
Path string
|
|
}
|
|
|
|
var _ CertWriter = &fsCertWriter{}
|
|
|
|
func (ops *FSCertWriterOptions) setDefaults() {
|
|
if ops.CertGenerator == nil {
|
|
ops.CertGenerator = &generator.SelfSignedCertGenerator{}
|
|
}
|
|
}
|
|
|
|
func (ops *FSCertWriterOptions) validate() error {
|
|
if len(ops.Path) == 0 {
|
|
return errors.New("path must be set in FSCertWriterOptions")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewFSCertWriter constructs a CertWriter that persists the certificate on filesystem.
|
|
func NewFSCertWriter(ops FSCertWriterOptions) (CertWriter, error) {
|
|
ops.setDefaults()
|
|
err := ops.validate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &fsCertWriter{
|
|
FSCertWriterOptions: &ops,
|
|
}, nil
|
|
}
|
|
|
|
// EnsureCert provisions certificates for a webhookClientConfig by writing the certificates in the filesystem.
|
|
func (f *fsCertWriter) EnsureCert(dnsName string) (*generator.Artifacts, bool, error) {
|
|
// create or refresh cert and write it to fs
|
|
f.dnsName = dnsName
|
|
return handleCommon(f.dnsName, f)
|
|
}
|
|
|
|
func (f *fsCertWriter) write() (*generator.Artifacts, error) {
|
|
return f.doWrite()
|
|
}
|
|
|
|
func (f *fsCertWriter) overwrite(_ string) (*generator.Artifacts, error) {
|
|
return f.doWrite()
|
|
}
|
|
|
|
func (f *fsCertWriter) doWrite() (*generator.Artifacts, error) {
|
|
certs, err := f.CertGenerator.Generate(f.dnsName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = WriteCertsToDir(f.Path, certs); err != nil {
|
|
return nil, err
|
|
}
|
|
return certs, nil
|
|
}
|
|
|
|
func WriteCertsToDir(path string, certs *generator.Artifacts) error {
|
|
// Writer's algorithm only manages files using symbolic link.
|
|
// If a file is not a symbolic link, will ignore the update for it.
|
|
// We want to cleanup for Writer by removing old files that are not symbolic links.
|
|
err := prepareToWrite(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aw, err := atomic.NewAtomicWriter(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return aw.Write(certToProjectionMap(certs))
|
|
}
|
|
|
|
// prepareToWrite ensures it directory is compatible with the atomic.Writer library.
|
|
func prepareToWrite(dir string) error {
|
|
_, err := os.Stat(dir)
|
|
switch {
|
|
case os.IsNotExist(err):
|
|
klog.Info("cert directory doesn't exist, creating", "directory", dir)
|
|
// TODO: figure out if we can reduce the permission. (Now it's 0777)
|
|
err = os.MkdirAll(dir, 0777)
|
|
if err != nil {
|
|
return fmt.Errorf("can't create dir: %v, err: %s", dir, err.Error())
|
|
}
|
|
case err != nil:
|
|
return err
|
|
}
|
|
|
|
filenames := []string{CAKeyName, CACertName, ServerCertName, ServerCertName2, ServerKeyName, ServerKeyName2}
|
|
for _, f := range filenames {
|
|
abspath := path.Join(dir, f)
|
|
_, err := os.Stat(abspath)
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
} else if err != nil {
|
|
klog.Error(err, "unable to stat file", "file", abspath)
|
|
}
|
|
_, err = os.Readlink(abspath)
|
|
// if it's not a symbolic link
|
|
if err != nil {
|
|
err = os.Remove(abspath)
|
|
if err != nil {
|
|
klog.Error(err, "unable to remove old file", "file", abspath)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fsCertWriter) read() (*generator.Artifacts, error) {
|
|
if err := ensureExist(f.Path); err != nil {
|
|
return nil, err
|
|
}
|
|
caKeyBytes, err := ioutil.ReadFile(path.Join(f.Path, CAKeyName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
caCertBytes, err := ioutil.ReadFile(path.Join(f.Path, CACertName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
certBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerCertName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerKeyName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &generator.Artifacts{
|
|
CAKey: caKeyBytes,
|
|
CACert: caCertBytes,
|
|
Cert: certBytes,
|
|
Key: keyBytes,
|
|
}, nil
|
|
}
|
|
|
|
func ensureExist(dir string) error {
|
|
filenames := []string{CAKeyName, CACertName, ServerCertName, ServerCertName2, ServerKeyName, ServerKeyName2}
|
|
for _, filename := range filenames {
|
|
_, err := os.Stat(path.Join(dir, filename))
|
|
switch {
|
|
case err == nil:
|
|
continue
|
|
case os.IsNotExist(err):
|
|
return notFoundError{err}
|
|
default:
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func certToProjectionMap(cert *generator.Artifacts) map[string]atomic.FileProjection {
|
|
// TODO: figure out if we can reduce the permission. (Now it's 0666)
|
|
return map[string]atomic.FileProjection{
|
|
CAKeyName: {
|
|
Data: cert.CAKey,
|
|
Mode: 0666,
|
|
},
|
|
CACertName: {
|
|
Data: cert.CACert,
|
|
Mode: 0666,
|
|
},
|
|
ServerCertName: {
|
|
Data: cert.Cert,
|
|
Mode: 0666,
|
|
},
|
|
ServerCertName2: {
|
|
Data: cert.Cert,
|
|
Mode: 0666,
|
|
},
|
|
ServerKeyName: {
|
|
Data: cert.Key,
|
|
Mode: 0666,
|
|
},
|
|
ServerKeyName2: {
|
|
Data: cert.Key,
|
|
Mode: 0666,
|
|
},
|
|
}
|
|
}
|