Skip to content

Commit

Permalink
repo credentials
Browse files Browse the repository at this point in the history
Signed-off-by: Lyndon-Li <[email protected]>
  • Loading branch information
Lyndon-Li committed Jul 29, 2022
1 parent 52fd18e commit b1464d1
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 41 deletions.
24 changes: 24 additions & 0 deletions internal/credentials/getter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Copyright the Velero contributors.
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 credentials

// CredentialGetter is a collection of interfaces for interacting with credentials
// that are stored in different targets
type CredentialGetter struct {
FromFile FileStore
FromSecret SecretStore
}
56 changes: 56 additions & 0 deletions internal/credentials/secret_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright the Velero contributors.
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 credentials

import (
"github.com/pkg/errors"
corev1api "k8s.io/api/core/v1"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/vmware-tanzu/velero/pkg/util/kube"
)

// SecretStore defines operations for interacting with credentials
// that are stored in Secret.
type SecretStore interface {
// Buffer returns the secret key defined by the given selector
Buffer(selector *corev1api.SecretKeySelector) (string, error)
}

type namespacedSecretStore struct {
client kbclient.Client
namespace string
}

// NewNamespacedSecretStore returns a SecretStore which can interact with credentials
// for the given namespace.
func NewNamespacedSecretStore(client kbclient.Client, namespace string) (SecretStore, error) {
return &namespacedSecretStore{
client: client,
namespace: namespace,
}, nil
}

// Buffer returns the secret key defined by the given selector.
func (n *namespacedSecretStore) Buffer(selector *corev1api.SecretKeySelector) (string, error) {
creds, err := kube.GetSecretKey(n.client, n.namespace, selector)
if err != nil {
return "", errors.Wrap(err, "unable to get key for secret")
}

return string(creds), nil
}
3 changes: 2 additions & 1 deletion pkg/cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import (
"github.com/vmware-tanzu/velero/internal/storage"
"github.com/vmware-tanzu/velero/internal/util/managercontroller"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
)

const (
Expand Down Expand Up @@ -519,7 +520,7 @@ func (s *server) initRestic() error {
}

// ensure the repo key secret is set up
if err := restic.EnsureCommonRepositoryKey(s.kubeClient.CoreV1(), s.namespace); err != nil {
if err := repokey.EnsureCommonRepositoryKey(s.kubeClient.CoreV1(), s.namespace); err != nil {
return err
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/controller/pod_volume_backup_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/vmware-tanzu/velero/internal/credentials"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/metrics"
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/kube"
Expand Down Expand Up @@ -324,7 +325,7 @@ func (r *PodVolumeBackupReconciler) buildResticCommand(ctx context.Context, log
log.WithField("path", path).Debugf("Found path matching glob")

// Temporary credentials.
details.credsFile, err = r.CredsFileStore.Path(restic.RepoKeySelector())
details.credsFile, err = r.CredsFileStore.Path(repokey.RepoKeySelector())
if err != nil {
return nil, errors.Wrap(err, "creating temporary Restic credentials file")
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/controller/pod_volume_restore_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (

"github.com/vmware-tanzu/velero/internal/credentials"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
Expand Down Expand Up @@ -241,7 +242,7 @@ func (c *PodVolumeRestoreReconciler) processRestore(ctx context.Context, req *ve
return errors.Wrap(err, "error identifying path of volume")
}

credsFile, err := c.credentialsFileStore.Path(restic.RepoKeySelector())
credsFile, err := c.credentialsFileStore.Path(repokey.RepoKeySelector())
if err != nil {
return errors.Wrap(err, "error creating temp restic credentials file")
}
Expand Down
75 changes: 75 additions & 0 deletions pkg/repository/keys/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright the Velero contributors.
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 keys

import (
"context"

"github.com/pkg/errors"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"

"github.com/vmware-tanzu/velero/pkg/builder"
)

const (
credentialsSecretName = "velero-restic-credentials"
credentialsKey = "repository-password"

encryptionKey = "static-passw0rd"
)

func EnsureCommonRepositoryKey(secretClient corev1client.SecretsGetter, namespace string) error {
_, err := secretClient.Secrets(namespace).Get(context.TODO(), credentialsSecretName, metav1.GetOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return errors.WithStack(err)
}
if err == nil {
return nil
}

// if we got here, we got an IsNotFound error, so we need to create the key

secret := &corev1api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: credentialsSecretName,
},
Type: corev1api.SecretTypeOpaque,
Data: map[string][]byte{
credentialsKey: []byte(encryptionKey),
},
}

if _, err = secretClient.Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
return errors.Wrapf(err, "error creating %s secret", credentialsSecretName)
}

return nil
}

// RepoKeySelector returns the SecretKeySelector which can be used to fetch
// the restic repository key.
func RepoKeySelector() *corev1api.SecretKeySelector {
// For now, all restic repos share the same key so we don't need the repoName to fetch it.
// When we move to full-backup encryption, we'll likely have a separate key per restic repo
// (all within the Velero server's namespace) so RepoKeySelector will need to select the key
// for that repo.
return builder.ForSecretKeySelector(credentialsSecretName, credentialsKey).Result()
}
30 changes: 30 additions & 0 deletions pkg/repository/keys/keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright the Velero contributors.
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 keys

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestRepoKeySelector(t *testing.T) {
selector := RepoKeySelector()

require.Equal(t, credentialsSecretName, selector.Name)
require.Equal(t, credentialsKey, selector.Key)
}
79 changes: 51 additions & 28 deletions pkg/repository/provider/unified_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ import (
"github.com/vmware-tanzu/velero/internal/credentials"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
repoconfig "github.com/vmware-tanzu/velero/pkg/repository/config"
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
"github.com/vmware-tanzu/velero/pkg/util/ownership"
)

type unifiedRepoProvider struct {
credentialsFileStore credentials.FileStore
workPath string
repoService udmrepo.BackupRepoService
log logrus.FieldLogger
credentialGetter credentials.CredentialGetter
workPath string
repoService udmrepo.BackupRepoService
log logrus.FieldLogger
}

// this func is assigned to a package-level variable so it can be
Expand All @@ -47,18 +48,30 @@ var getGCPCredentials = repoconfig.GetGCPCredentials
var getS3BucketRegion = repoconfig.GetAWSBucketRegion
var getAzureStorageDomain = repoconfig.GetAzureStorageDomain

type localFuncTable struct {
getRepoPassword func(credentials.SecretStore, RepoParam) (string, error)
getStorageVariables func(*velerov1api.BackupStorageLocation, string) (map[string]string, error)
getStorageCredentials func(*velerov1api.BackupStorageLocation, credentials.FileStore) (map[string]string, error)
}

var funcTable = localFuncTable{
getRepoPassword: getRepoPassword,
getStorageVariables: getStorageVariables,
getStorageCredentials: getStorageCredentials,
}

// NewUnifiedRepoProvider creates the service provider for Unified Repo
// workPath is the path for Unified Repo to store some local information
// workPath could be empty, if so, the default path will be used
func NewUnifiedRepoProvider(
credentialFileStore credentials.FileStore,
credentialGetter credentials.CredentialGetter,
workPath string,
log logrus.FieldLogger,
) (Provider, error) {
repo := unifiedRepoProvider{
credentialsFileStore: credentialFileStore,
workPath: workPath,
log: log,
credentialGetter: credentialGetter,
workPath: workPath,
log: log,
}

repo.repoService = createRepoService(log)
Expand Down Expand Up @@ -120,15 +133,38 @@ func (urp *unifiedRepoProvider) Forget(ctx context.Context, snapshotID string, p
return nil
}

func (urp *unifiedRepoProvider) getRepoPassword(param RepoParam) (string, error) {
///TODO: get repo password
func getRepoPassword(secretStore credentials.SecretStore, param RepoParam) (string, error) {
if secretStore == nil {
return "", errors.New("invalid credentials interface")
}

buf, err := secretStore.Buffer(repokey.RepoKeySelector())
if err != nil {
return "", errors.Wrap(err, "error to get password buffer")
}

return "", nil
return strings.TrimSpace(string(buf)), nil
}

func (urp *unifiedRepoProvider) getRepoOption(param RepoParam) (udmrepo.RepoOptions, error) {
repoPassword, err := funcTable.getRepoPassword(urp.credentialGetter.FromSecret, param)
if err != nil {
return udmrepo.RepoOptions{}, errors.Wrap(err, "error to get repo password")
}

storeVar, err := funcTable.getStorageVariables(param.BackupLocation, param.SubDir)
if err != nil {
return udmrepo.RepoOptions{}, errors.Wrap(err, "error to get storage variables")
}

storeCred, err := funcTable.getStorageCredentials(param.BackupLocation, urp.credentialGetter.FromFile)
if err != nil {
return udmrepo.RepoOptions{}, errors.Wrap(err, "error to get repo credentials")
}

repoOption := udmrepo.RepoOptions{
StorageType: getStorageType(param.BackupLocation),
RepoPassword: repoPassword,
ConfigFilePath: getRepoConfigFile(urp.workPath, string(param.BackupLocation.UID)),
Ownership: udmrepo.OwnershipOptions{
Username: ownership.GetRepositoryOwner().Username,
Expand All @@ -138,27 +174,10 @@ func (urp *unifiedRepoProvider) getRepoOption(param RepoParam) (udmrepo.RepoOpti
GeneralOptions: make(map[string]string),
}

repoPassword, err := urp.getRepoPassword(param)
if err != nil {
return repoOption, errors.Wrap(err, "error to get repo password")
}

repoOption.RepoPassword = repoPassword

storeVar, err := getStorageVariables(param.BackupLocation, param.SubDir)
if err != nil {
return repoOption, errors.Wrap(err, "error to get storage variables")
}

for k, v := range storeVar {
repoOption.StorageOptions[k] = v
}

storeCred, err := getStorageCredentials(param.BackupLocation, urp.credentialsFileStore)
if err != nil {
return repoOption, errors.Wrap(err, "error to get repo credential env")
}

for k, v := range storeCred {
repoOption.StorageOptions[k] = v
}
Expand Down Expand Up @@ -187,6 +206,10 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr
result := make(map[string]string)
var err error

if credentialsFileStore == nil {
return map[string]string{}, errors.New("invalid credentials interface")
}

backendType := repoconfig.GetBackendType(backupLocation.Spec.Provider)
if !repoconfig.IsBackendTypeValid(backendType) {
return map[string]string{}, errors.New("invalid storage provider")
Expand Down
Loading

0 comments on commit b1464d1

Please sign in to comment.