mirror of https://github.com/kubernetes/kops.git
192 lines
4.5 KiB
Go
192 lines
4.5 KiB
Go
/*
|
|
Copyright 2019 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 secrets
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/kops/pkg/acls"
|
|
"k8s.io/kops/pkg/apis/kops"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/util/pkg/vfs"
|
|
)
|
|
|
|
type VFSSecretStore struct {
|
|
VFSSecretStoreReader
|
|
cluster *kops.Cluster
|
|
}
|
|
|
|
var _ fi.SecretStore = &VFSSecretStore{}
|
|
|
|
func NewVFSSecretStore(cluster *kops.Cluster, basedir vfs.Path) fi.SecretStore {
|
|
c := &VFSSecretStore{
|
|
VFSSecretStoreReader: VFSSecretStoreReader{
|
|
basedir: basedir,
|
|
},
|
|
cluster: cluster,
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *VFSSecretStore) MirrorTo(ctx context.Context, basedir vfs.Path) error {
|
|
if basedir.Path() == c.basedir.Path() {
|
|
klog.V(2).Infof("Skipping mirror of secret store from %q to %q (same path)", c.basedir, basedir)
|
|
return nil
|
|
}
|
|
klog.V(2).Infof("Mirroring secret store from %q to %q", c.basedir, basedir)
|
|
|
|
secrets, err := c.ListSecrets()
|
|
if err != nil {
|
|
return fmt.Errorf("error listing secrets for mirror: %v", err)
|
|
}
|
|
|
|
for _, name := range secrets {
|
|
secret, err := c.FindSecret(name)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading secret %q for mirror: %v", name, err)
|
|
}
|
|
|
|
if secret == nil {
|
|
return fmt.Errorf("unable to find secret %q for mirror", name)
|
|
}
|
|
|
|
p := BuildVfsSecretPath(basedir, name)
|
|
|
|
acl, err := acls.GetACL(ctx, p, c.cluster)
|
|
if err != nil {
|
|
return fmt.Errorf("error building acl for secret %q for mirror: %v", name, err)
|
|
}
|
|
|
|
klog.Infof("mirroring secret %s -> %s", name, p)
|
|
|
|
err = createSecret(ctx, secret, p, acl, true)
|
|
if err != nil {
|
|
return fmt.Errorf("error writing secret %q for mirror: %v", name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteSecret implements fi.SecretStore DeleteSecret
|
|
func (c *VFSSecretStore) DeleteSecret(name string) error {
|
|
ctx := context.TODO()
|
|
|
|
p := c.buildSecretPath(name)
|
|
return p.Remove(ctx)
|
|
}
|
|
|
|
func (c *VFSSecretStore) ListSecrets() ([]string, error) {
|
|
files, err := c.basedir.ReadDir()
|
|
var ids []string
|
|
if os.IsNotExist(err) {
|
|
return ids, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing secrets directory: %v", err)
|
|
}
|
|
for _, f := range files {
|
|
id := f.Base()
|
|
ids = append(ids, id)
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
func (c *VFSSecretStore) GetOrCreateSecret(ctx context.Context, id string, secret *fi.Secret) (*fi.Secret, bool, error) {
|
|
p := c.buildSecretPath(id)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
s, err := c.FindSecret(id)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if s != nil {
|
|
return s, false, nil
|
|
}
|
|
|
|
acl, err := acls.GetACL(ctx, p, c.cluster)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
err = createSecret(ctx, secret, p, acl, false)
|
|
if err != nil {
|
|
if os.IsExist(err) && i == 0 {
|
|
klog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")
|
|
continue
|
|
} else {
|
|
return nil, false, err
|
|
}
|
|
}
|
|
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Make double-sure it round-trips
|
|
s, err := c.loadSecret(ctx, p)
|
|
if err != nil {
|
|
klog.Fatalf("unable to load secret immediately after creation %v: %v", p, err)
|
|
return nil, false, err
|
|
}
|
|
return s, true, nil
|
|
}
|
|
|
|
func (c *VFSSecretStore) ReplaceSecret(id string, secret *fi.Secret) (*fi.Secret, error) {
|
|
ctx := context.TODO()
|
|
|
|
p := c.buildSecretPath(id)
|
|
|
|
acl, err := acls.GetACL(ctx, p, c.cluster)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = createSecret(ctx, secret, p, acl, true)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to write secret: %v", err)
|
|
}
|
|
|
|
// Confirm the secret exists
|
|
s, err := c.loadSecret(ctx, p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to load secret immediately after creation %v: %v", p, err)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// createSecret will create the Secret, overwriting an existing secret if replace is true
|
|
func createSecret(ctx context.Context, s *fi.Secret, p vfs.Path, acl vfs.ACL, replace bool) error {
|
|
data, err := json.Marshal(s)
|
|
if err != nil {
|
|
return fmt.Errorf("error serializing secret: %v", err)
|
|
}
|
|
|
|
rs := bytes.NewReader(data)
|
|
if replace {
|
|
return p.WriteFile(ctx, rs, acl)
|
|
}
|
|
return p.CreateFile(ctx, rs, acl)
|
|
}
|