add service mesh controller
add service mesh metrics remove unused circle yaml fix travis misconfiguration fix travis misconfiguration fix travis misconfiguration
This commit is contained in:
453
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic/atomic_writer.go
generated
vendored
Normal file
453
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic/atomic_writer.go
generated
vendored
Normal file
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
Copyright 2016 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 atomic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFileNameLength = 255
|
||||
maxPathLength = 4096
|
||||
)
|
||||
|
||||
// AtomicWriter handles atomically projecting content for a set of files into
|
||||
// a target directory.
|
||||
//
|
||||
// Note:
|
||||
//
|
||||
// 1. AtomicWriter reserves the set of pathnames starting with `..`.
|
||||
// 2. AtomicWriter offers no concurrency guarantees and must be synchronized
|
||||
// by the caller.
|
||||
//
|
||||
// The visible files in this volume are symlinks to files in the writer's data
|
||||
// directory. Actual files are stored in a hidden timestamped directory which
|
||||
// is symlinked to by the data directory. The timestamped directory and
|
||||
// data directory symlink are created in the writer's target dir. This scheme
|
||||
// allows the files to be atomically updated by changing the target of the
|
||||
// data directory symlink.
|
||||
//
|
||||
// Consumers of the target directory can monitor the ..data symlink using
|
||||
// inotify or fanotify to receive events when the content in the volume is
|
||||
// updated.
|
||||
type AtomicWriter struct {
|
||||
targetDir string
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
type FileProjection struct {
|
||||
Data []byte
|
||||
Mode int32
|
||||
}
|
||||
|
||||
// NewAtomicWriter creates a new AtomicWriter configured to write to the given
|
||||
// target directory, or returns an error if the target directory does not exist.
|
||||
func NewAtomicWriter(targetDir string, log logr.Logger) (*AtomicWriter, error) {
|
||||
_, err := os.Stat(targetDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AtomicWriter{targetDir: targetDir, log: log}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
dataDirName = "..data"
|
||||
newDataDirName = "..data_tmp"
|
||||
)
|
||||
|
||||
// Write does an atomic projection of the given payload into the writer's target
|
||||
// directory. Input paths must not begin with '..'.
|
||||
//
|
||||
// The Write algorithm is:
|
||||
//
|
||||
// 1. The payload is validated; if the payload is invalid, the function returns
|
||||
// 2. The current timestamped directory is detected by reading the data directory
|
||||
// symlink
|
||||
// 3. The old version of the volume is walked to determine whether any
|
||||
// portion of the payload was deleted and is still present on disk.
|
||||
// 4. The data in the current timestamped directory is compared to the projected
|
||||
// data to determine if an update is required.
|
||||
// 5. A new timestamped dir is created
|
||||
// 6. The payload is written to the new timestamped directory
|
||||
// 7. Symlinks and directory for new user-visible files are created (if needed).
|
||||
//
|
||||
// For example, consider the files:
|
||||
// <target-dir>/podName
|
||||
// <target-dir>/user/labels
|
||||
// <target-dir>/k8s/annotations
|
||||
//
|
||||
// The user visible files are symbolic links into the internal data directory:
|
||||
// <target-dir>/podName -> ..data/podName
|
||||
// <target-dir>/usr -> ..data/usr
|
||||
// <target-dir>/k8s -> ..data/k8s
|
||||
//
|
||||
// The data directory itself is a link to a timestamped directory with
|
||||
// the real data:
|
||||
// <target-dir>/..data -> ..2016_02_01_15_04_05.12345678/
|
||||
// 8. A symlink to the new timestamped directory ..data_tmp is created that will
|
||||
// become the new data directory
|
||||
// 9. The new data directory symlink is renamed to the data directory; rename is atomic
|
||||
// 10. Old paths are removed from the user-visible portion of the target directory
|
||||
// 11. The previous timestamped directory is removed, if it exists
|
||||
func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
||||
// (1)
|
||||
cleanPayload, err := validatePayload(payload)
|
||||
if err != nil {
|
||||
w.log.Error(err, "invalid payload")
|
||||
return err
|
||||
}
|
||||
|
||||
// (2)
|
||||
dataDirPath := path.Join(w.targetDir, dataDirName)
|
||||
oldTsDir, err := os.Readlink(dataDirPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
w.log.Error(err, "unable to read link for data directory")
|
||||
return err
|
||||
}
|
||||
// although Readlink() returns "" on err, don't be fragile by relying on it (since it's not specified in docs)
|
||||
// empty oldTsDir indicates that it didn't exist
|
||||
oldTsDir = ""
|
||||
}
|
||||
oldTsPath := path.Join(w.targetDir, oldTsDir)
|
||||
|
||||
var pathsToRemove sets.String
|
||||
// if there was no old version, there's nothing to remove
|
||||
if len(oldTsDir) != 0 {
|
||||
// (3)
|
||||
pathsToRemove, err = w.pathsToRemove(cleanPayload, oldTsPath)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to determine user-visible files to remove")
|
||||
return err
|
||||
}
|
||||
|
||||
// (4)
|
||||
if should, err := shouldWritePayload(cleanPayload, oldTsPath); err != nil {
|
||||
w.log.Error(err, "unable to determine whether payload should be written to disk")
|
||||
return err
|
||||
} else if !should && len(pathsToRemove) == 0 {
|
||||
w.log.V(1).Info("no update required for target directory", "directory", w.targetDir)
|
||||
return nil
|
||||
} else {
|
||||
w.log.V(1).Info("write required for target directory", "directory", w.targetDir)
|
||||
}
|
||||
}
|
||||
|
||||
// (5)
|
||||
tsDir, err := w.newTimestampDir()
|
||||
if err != nil {
|
||||
w.log.Error(err, "error creating new ts data directory")
|
||||
return err
|
||||
}
|
||||
tsDirName := filepath.Base(tsDir)
|
||||
|
||||
// (6)
|
||||
if err = w.writePayloadToDir(cleanPayload, tsDir); err != nil {
|
||||
w.log.Error(err, "unable to write payload to ts data directory", "ts directory", tsDir)
|
||||
return err
|
||||
} else {
|
||||
w.log.V(1).Info("performed write of new data to ts data directory", "ts directory", tsDir)
|
||||
}
|
||||
|
||||
// (7)
|
||||
if err = w.createUserVisibleFiles(cleanPayload); err != nil {
|
||||
w.log.Error(err, "unable to create visible symlinks in target directory", "target directory", w.targetDir)
|
||||
return err
|
||||
}
|
||||
|
||||
// (8)
|
||||
newDataDirPath := path.Join(w.targetDir, newDataDirName)
|
||||
if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
|
||||
os.RemoveAll(tsDir)
|
||||
w.log.Error(err, "unable to create symbolic link for atomic update")
|
||||
return err
|
||||
}
|
||||
|
||||
// (9)
|
||||
if runtime.GOOS == "windows" {
|
||||
os.Remove(dataDirPath)
|
||||
err = os.Symlink(tsDirName, dataDirPath)
|
||||
os.Remove(newDataDirPath)
|
||||
} else {
|
||||
err = os.Rename(newDataDirPath, dataDirPath)
|
||||
}
|
||||
if err != nil {
|
||||
os.Remove(newDataDirPath)
|
||||
os.RemoveAll(tsDir)
|
||||
w.log.Error(err, "unable to rename symbolic link for data directory", "data directory", newDataDirPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// (10)
|
||||
if err = w.removeUserVisiblePaths(pathsToRemove); err != nil {
|
||||
w.log.Error(err, "unable to remove old visible symlinks")
|
||||
return err
|
||||
}
|
||||
|
||||
// (11)
|
||||
if len(oldTsDir) > 0 {
|
||||
if err = os.RemoveAll(oldTsPath); err != nil {
|
||||
w.log.Error(err, "unable to remove old data directory", "data directory", oldTsDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePayload returns an error if any path in the payload returns a copy of the payload with the paths cleaned.
|
||||
func validatePayload(payload map[string]FileProjection) (map[string]FileProjection, error) {
|
||||
cleanPayload := make(map[string]FileProjection)
|
||||
for k, content := range payload {
|
||||
if err := validatePath(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanPayload[filepath.Clean(k)] = content
|
||||
}
|
||||
|
||||
return cleanPayload, nil
|
||||
}
|
||||
|
||||
// validatePath validates a single path, returning an error if the path is
|
||||
// invalid. paths may not:
|
||||
//
|
||||
// 1. be absolute
|
||||
// 2. contain '..' as an element
|
||||
// 3. start with '..'
|
||||
// 4. contain filenames larger than 255 characters
|
||||
// 5. be longer than 4096 characters
|
||||
func validatePath(targetPath string) error {
|
||||
// TODO: somehow unify this with the similar api validation,
|
||||
// validateVolumeSourcePath; the error semantics are just different enough
|
||||
// from this that it was time-prohibitive trying to find the right
|
||||
// refactoring to re-use.
|
||||
if targetPath == "" {
|
||||
return fmt.Errorf("invalid path: must not be empty: %q", targetPath)
|
||||
}
|
||||
if path.IsAbs(targetPath) {
|
||||
return fmt.Errorf("invalid path: must be relative path: %s", targetPath)
|
||||
}
|
||||
|
||||
if len(targetPath) > maxPathLength {
|
||||
return fmt.Errorf("invalid path: must be less than or equal to %d characters", maxPathLength)
|
||||
}
|
||||
|
||||
items := strings.Split(targetPath, string(os.PathSeparator))
|
||||
for _, item := range items {
|
||||
if item == ".." {
|
||||
return fmt.Errorf("invalid path: must not contain '..': %s", targetPath)
|
||||
}
|
||||
if len(item) > maxFileNameLength {
|
||||
return fmt.Errorf("invalid path: filenames must be less than or equal to %d characters", maxFileNameLength)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(items[0], "..") && len(items[0]) > 2 {
|
||||
return fmt.Errorf("invalid path: must not start with '..': %s", targetPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldWritePayload returns whether the payload should be written to disk.
|
||||
func shouldWritePayload(payload map[string]FileProjection, oldTsDir string) (bool, error) {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
shouldWrite, err := shouldWriteFile(path.Join(oldTsDir, userVisiblePath), fileProjection.Data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if shouldWrite {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// shouldWriteFile returns whether a new version of a file should be written to disk.
|
||||
func shouldWriteFile(path string, content []byte) (bool, error) {
|
||||
_, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
contentOnFs, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return (bytes.Compare(content, contentOnFs) != 0), nil
|
||||
}
|
||||
|
||||
// pathsToRemove walks the current version of the data directory and
|
||||
// determines which paths should be removed (if any) after the payload is
|
||||
// written to the target directory.
|
||||
func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir string) (sets.String, error) {
|
||||
paths := sets.NewString()
|
||||
visitor := func(path string, info os.FileInfo, err error) error {
|
||||
relativePath := strings.TrimPrefix(path, oldTsDir)
|
||||
relativePath = strings.TrimPrefix(relativePath, string(os.PathSeparator))
|
||||
if relativePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
paths.Insert(relativePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(oldTsDir, visitor)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.log.V(1).Info("current paths", "target directory", w.targetDir, "paths", paths.List())
|
||||
|
||||
newPaths := sets.NewString()
|
||||
for file := range payload {
|
||||
// add all subpaths for the payload to the set of new paths
|
||||
// to avoid attempting to remove non-empty dirs
|
||||
for subPath := file; subPath != ""; {
|
||||
newPaths.Insert(subPath)
|
||||
subPath, _ = filepath.Split(subPath)
|
||||
subPath = strings.TrimSuffix(subPath, string(os.PathSeparator))
|
||||
}
|
||||
}
|
||||
w.log.V(1).Info("new paths", "target directory", w.targetDir, "paths", newPaths.List())
|
||||
|
||||
result := paths.Difference(newPaths)
|
||||
w.log.V(1).Info("paths to remove", "target directory", w.targetDir, "paths", result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTimestampDir creates a new timestamp directory
|
||||
func (w *AtomicWriter) newTimestampDir() (string, error) {
|
||||
tsDir, err := ioutil.TempDir(w.targetDir, time.Now().UTC().Format("..2006_01_02_15_04_05."))
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to create new temp directory")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 0755 permissions are needed to allow 'group' and 'other' to recurse the
|
||||
// directory tree. do a chmod here to ensure that permissions are set correctly
|
||||
// regardless of the process' umask.
|
||||
err = os.Chmod(tsDir, 0755)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to set mode on new temp directory")
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tsDir, nil
|
||||
}
|
||||
|
||||
// writePayloadToDir writes the given payload to the given directory. The
|
||||
// directory must exist.
|
||||
func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir string) error {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
content := fileProjection.Data
|
||||
mode := os.FileMode(fileProjection.Mode)
|
||||
fullPath := path.Join(dir, userVisiblePath)
|
||||
baseDir, _ := filepath.Split(fullPath)
|
||||
|
||||
err := os.MkdirAll(baseDir, os.ModePerm)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to create directory", "directory", baseDir)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(fullPath, content, mode)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to write file", "file", fullPath, "mode", mode)
|
||||
return err
|
||||
}
|
||||
// Chmod is needed because ioutil.WriteFile() ends up calling
|
||||
// open(2) to create the file, so the final mode used is "mode &
|
||||
// ~umask". But we want to make sure the specified mode is used
|
||||
// in the file no matter what the umask is.
|
||||
err = os.Chmod(fullPath, mode)
|
||||
if err != nil {
|
||||
w.log.Error(err, "unable to write file", "file", fullPath, "mode", mode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createUserVisibleFiles creates the relative symlinks for all the
|
||||
// files configured in the payload. If the directory in a file path does not
|
||||
// exist, it is created.
|
||||
//
|
||||
// Viz:
|
||||
// For files: "bar", "foo/bar", "baz/bar", "foo/baz/blah"
|
||||
// the following symlinks are created:
|
||||
// bar -> ..data/bar
|
||||
// foo -> ..data/foo
|
||||
// baz -> ..data/baz
|
||||
func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) error {
|
||||
for userVisiblePath := range payload {
|
||||
slashpos := strings.Index(userVisiblePath, string(os.PathSeparator))
|
||||
if slashpos == -1 {
|
||||
slashpos = len(userVisiblePath)
|
||||
}
|
||||
linkname := userVisiblePath[:slashpos]
|
||||
_, err := os.Readlink(path.Join(w.targetDir, linkname))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// The link into the data directory for this path doesn't exist; create it
|
||||
visibleFile := path.Join(w.targetDir, linkname)
|
||||
dataDirFile := path.Join(dataDirName, linkname)
|
||||
|
||||
err = os.Symlink(dataDirFile, visibleFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeUserVisiblePaths removes the set of paths from the user-visible
|
||||
// portion of the writer's target directory.
|
||||
func (w *AtomicWriter) removeUserVisiblePaths(paths sets.String) error {
|
||||
ps := string(os.PathSeparator)
|
||||
var lasterr error
|
||||
for p := range paths {
|
||||
// only remove symlinks from the volume root directory (i.e. items that don't contain '/')
|
||||
if strings.Contains(p, ps) {
|
||||
continue
|
||||
}
|
||||
if err := os.Remove(path.Join(w.targetDir, p)); err != nil {
|
||||
w.log.Error(err, "unable to prune old user-visible path", "path", p)
|
||||
lasterr = err
|
||||
}
|
||||
}
|
||||
|
||||
return lasterr
|
||||
}
|
||||
137
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go
generated
vendored
Normal file
137
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
)
|
||||
|
||||
const (
|
||||
// CAKeyName is the name of the CA private key
|
||||
CAKeyName = "ca-key.pem"
|
||||
// CACertName is the name of the CA certificate
|
||||
CACertName = "ca-cert.pem"
|
||||
// ServerKeyName is the name of the server private key
|
||||
ServerKeyName = "key.pem"
|
||||
// ServerCertName is the name of the serving certificate
|
||||
ServerCertName = "cert.pem"
|
||||
)
|
||||
|
||||
// CertWriter provides method to handle webhooks.
|
||||
type CertWriter interface {
|
||||
// EnsureCert provisions the cert for the webhookClientConfig.
|
||||
EnsureCert(dnsName string) (*generator.Artifacts, bool, error)
|
||||
// Inject injects the necessary information given the objects.
|
||||
// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
|
||||
Inject(objs ...runtime.Object) error
|
||||
}
|
||||
|
||||
// handleCommon ensures the given webhook has a proper certificate.
|
||||
// It uses the given certReadWriter to read and (or) write the certificate.
|
||||
func handleCommon(dnsName string, ch certReadWriter) (*generator.Artifacts, bool, error) {
|
||||
if len(dnsName) == 0 {
|
||||
return nil, false, errors.New("dnsName should not be empty")
|
||||
}
|
||||
if ch == nil {
|
||||
return nil, false, errors.New("certReaderWriter should not be nil")
|
||||
}
|
||||
|
||||
certs, changed, err := createIfNotExists(ch)
|
||||
if err != nil {
|
||||
return nil, changed, err
|
||||
}
|
||||
|
||||
// Recreate the cert if it's invalid.
|
||||
valid := validCert(certs, dnsName)
|
||||
if !valid {
|
||||
log.Info("cert is invalid or expiring, regenerating a new one")
|
||||
certs, err = ch.overwrite()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
return certs, changed, nil
|
||||
}
|
||||
|
||||
func createIfNotExists(ch certReadWriter) (*generator.Artifacts, bool, error) {
|
||||
// Try to read first
|
||||
certs, err := ch.read()
|
||||
if isNotFound(err) {
|
||||
// Create if not exists
|
||||
certs, err = ch.write()
|
||||
switch {
|
||||
// This may happen if there is another racer.
|
||||
case isAlreadyExists(err):
|
||||
certs, err = ch.read()
|
||||
return certs, true, err
|
||||
default:
|
||||
return certs, true, err
|
||||
}
|
||||
}
|
||||
return certs, false, err
|
||||
}
|
||||
|
||||
// certReadWriter provides methods for reading and writing certificates.
|
||||
type certReadWriter interface {
|
||||
// read reads a webhook name and returns the certs for it.
|
||||
read() (*generator.Artifacts, error)
|
||||
// write writes the certs and return the certs it wrote.
|
||||
write() (*generator.Artifacts, error)
|
||||
// overwrite overwrites the existing certs and return the certs it wrote.
|
||||
overwrite() (*generator.Artifacts, error)
|
||||
}
|
||||
|
||||
func validCert(certs *generator.Artifacts, dnsName string) bool {
|
||||
if certs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify key and cert are valid pair
|
||||
_, err := tls.X509KeyPair(certs.Cert, certs.Key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify cert is good for desired DNS name and signed by CA and will be valid for desired period of time.
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(certs.CACert) {
|
||||
return false
|
||||
}
|
||||
block, _ := pem.Decode([]byte(certs.Cert))
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ops := x509.VerifyOptions{
|
||||
DNSName: dnsName,
|
||||
Roots: pool,
|
||||
CurrentTime: time.Now().AddDate(0, 6, 0),
|
||||
}
|
||||
_, err = cert.Verify(ops)
|
||||
return err == nil
|
||||
}
|
||||
64
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go
generated
vendored
Normal file
64
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 provides method to provision and persist the certificates.
|
||||
|
||||
It will create the certificates if they don't exist.
|
||||
It will ensure the certificates are valid and not expiring. If not, it will recreate them.
|
||||
|
||||
Create a CertWriter that can write the certificate to secret
|
||||
|
||||
writer, err := NewSecretCertWriter(SecretCertWriterOptions{
|
||||
Secret: types.NamespacedName{Namespace: "foo", Name: "bar"},
|
||||
Client: client,
|
||||
})
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
|
||||
Create a CertWriter that can write the certificate to the filesystem.
|
||||
|
||||
writer, err := NewFSCertWriter(FSCertWriterOptions{
|
||||
Path: "path/to/cert/",
|
||||
})
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
|
||||
Provision the certificates using the CertWriter. The certificate will be available in the desired secret or
|
||||
the desired path.
|
||||
|
||||
// writer can be either one of the CertWriters created above
|
||||
certs, changed, err := writer.EnsureCerts("admissionwebhook.k8s.io", false)
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
|
||||
Inject necessary information given the objects.
|
||||
|
||||
err = writer.Inject(objs...)
|
||||
if err != nil {
|
||||
// handler error
|
||||
}
|
||||
*/
|
||||
package writer
|
||||
|
||||
import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
|
||||
)
|
||||
|
||||
var log = logf.KBLog.WithName("admission").WithName("cert").WithName("writer")
|
||||
43
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/error.go
generated
vendored
Normal file
43
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/error.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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
|
||||
|
||||
type notFoundError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e notFoundError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func isNotFound(err error) bool {
|
||||
_, ok := err.(notFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type alreadyExistError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e alreadyExistError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func isAlreadyExists(err error) bool {
|
||||
_, ok := err.(alreadyExistError)
|
||||
return ok
|
||||
}
|
||||
216
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go
generated
vendored
Normal file
216
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
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/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic"
|
||||
)
|
||||
|
||||
// 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() (*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
|
||||
}
|
||||
|
||||
// AtomicWriter'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 AtomicWriter by removing old files that are not symbolic links.
|
||||
err = prepareToWrite(f.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aw, err := atomic.NewAtomicWriter(f.Path, log.WithName("atomic-writer").
|
||||
WithValues("task", "processing webhook"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = aw.Write(certToProjectionMap(certs))
|
||||
return certs, err
|
||||
}
|
||||
|
||||
// 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):
|
||||
log.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", dir)
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
filenames := []string{CAKeyName, CACertName, ServerCertName, ServerKeyName}
|
||||
for _, f := range filenames {
|
||||
abspath := path.Join(dir, f)
|
||||
_, err := os.Stat(abspath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.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 {
|
||||
log.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, ServerKeyName}
|
||||
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,
|
||||
},
|
||||
ServerKeyName: {
|
||||
Data: cert.Key,
|
||||
Mode: 0666,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fsCertWriter) Inject(objs ...runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
184
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go
generated
vendored
Normal file
184
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
|
||||
)
|
||||
|
||||
// secretCertWriter provisions the certificate by reading and writing to the k8s secrets.
|
||||
type secretCertWriter struct {
|
||||
*SecretCertWriterOptions
|
||||
|
||||
// dnsName is the DNS name that the certificate is for.
|
||||
dnsName string
|
||||
}
|
||||
|
||||
// SecretCertWriterOptions is options for constructing a secretCertWriter.
|
||||
type SecretCertWriterOptions struct {
|
||||
// client talks to a kubernetes cluster for creating the secret.
|
||||
Client client.Client
|
||||
// certGenerator generates the certificates.
|
||||
CertGenerator generator.CertGenerator
|
||||
// secret points the secret that contains certificates that written by the CertWriter.
|
||||
Secret *types.NamespacedName
|
||||
}
|
||||
|
||||
var _ CertWriter = &secretCertWriter{}
|
||||
|
||||
func (ops *SecretCertWriterOptions) setDefaults() {
|
||||
if ops.CertGenerator == nil {
|
||||
ops.CertGenerator = &generator.SelfSignedCertGenerator{}
|
||||
}
|
||||
}
|
||||
|
||||
func (ops *SecretCertWriterOptions) validate() error {
|
||||
if ops.Client == nil {
|
||||
return errors.New("client must be set in SecretCertWriterOptions")
|
||||
}
|
||||
if ops.Secret == nil {
|
||||
return errors.New("secret must be set in SecretCertWriterOptions")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSecretCertWriter constructs a CertWriter that persists the certificate in a k8s secret.
|
||||
func NewSecretCertWriter(ops SecretCertWriterOptions) (CertWriter, error) {
|
||||
ops.setDefaults()
|
||||
err := ops.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &secretCertWriter{
|
||||
SecretCertWriterOptions: &ops,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EnsureCert provisions certificates for a webhookClientConfig by writing the certificates to a k8s secret.
|
||||
func (s *secretCertWriter) EnsureCert(dnsName string) (*generator.Artifacts, bool, error) {
|
||||
// Create or refresh the certs based on clientConfig
|
||||
s.dnsName = dnsName
|
||||
return handleCommon(s.dnsName, s)
|
||||
}
|
||||
|
||||
var _ certReadWriter = &secretCertWriter{}
|
||||
|
||||
func (s *secretCertWriter) buildSecret() (*corev1.Secret, *generator.Artifacts, error) {
|
||||
certs, err := s.CertGenerator.Generate(s.dnsName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
secret := certsToSecret(certs, *s.Secret)
|
||||
return secret, certs, err
|
||||
}
|
||||
|
||||
func (s *secretCertWriter) write() (*generator.Artifacts, error) {
|
||||
secret, certs, err := s.buildSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.Client.Create(nil, secret)
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
return nil, alreadyExistError{err}
|
||||
}
|
||||
return certs, err
|
||||
}
|
||||
|
||||
func (s *secretCertWriter) overwrite() (
|
||||
*generator.Artifacts, error) {
|
||||
secret, certs, err := s.buildSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.Client.Update(nil, secret)
|
||||
return certs, err
|
||||
}
|
||||
|
||||
func (s *secretCertWriter) read() (*generator.Artifacts, error) {
|
||||
secret := &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
}
|
||||
err := s.Client.Get(nil, *s.Secret, secret)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, notFoundError{err}
|
||||
}
|
||||
certs := secretToCerts(secret)
|
||||
if certs != nil {
|
||||
// Store the CA for next usage.
|
||||
s.CertGenerator.SetCA(certs.CAKey, certs.CACert)
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
func secretToCerts(secret *corev1.Secret) *generator.Artifacts {
|
||||
if secret.Data == nil {
|
||||
return nil
|
||||
}
|
||||
return &generator.Artifacts{
|
||||
CAKey: secret.Data[CAKeyName],
|
||||
CACert: secret.Data[CACertName],
|
||||
Cert: secret.Data[ServerCertName],
|
||||
Key: secret.Data[ServerKeyName],
|
||||
}
|
||||
}
|
||||
|
||||
func certsToSecret(certs *generator.Artifacts, sec types.NamespacedName) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: sec.Namespace,
|
||||
Name: sec.Name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
CAKeyName: certs.CAKey,
|
||||
CACertName: certs.CACert,
|
||||
ServerKeyName: certs.Key,
|
||||
ServerCertName: certs.Cert,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Inject sets the ownerReference in the secret.
|
||||
func (s *secretCertWriter) Inject(objs ...runtime.Object) error {
|
||||
// TODO: figure out how to get the UID
|
||||
//for i := range objs {
|
||||
// accessor, err := meta.Accessor(objs[i])
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// err = controllerutil.SetControllerReference(accessor, s.sec, scheme.Scheme)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
//return s.client.Update(context.Background(), s.sec)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user