Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce encrypted volume store support #43

Merged
merged 3 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions internal/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/portainer/k2d/internal/adapter/converter"
"github.com/portainer/k2d/internal/adapter/store"
"github.com/portainer/k2d/internal/adapter/store/filesystem"
"github.com/portainer/k2d/internal/adapter/store/memory"
"github.com/portainer/k2d/internal/adapter/store/volume"
k2dtypes "github.com/portainer/k2d/internal/adapter/types"
"github.com/portainer/k2d/internal/config"
Expand Down Expand Up @@ -79,8 +78,9 @@ func NewKubeDockerAdapter(options *KubeDockerAdapterOptions) (*KubeDockerAdapter
}

storeOptions := store.StoreOptions{
Backend: options.K2DConfig.StoreBackend,
Logger: options.Logger,
Backend: options.K2DConfig.StoreBackend,
RegistryBackend: options.K2DConfig.StoreRegistryBackend,
Logger: options.Logger,
Filesystem: filesystem.FileSystemStoreOptions{
DataPath: options.K2DConfig.DataPath,
},
Expand All @@ -95,6 +95,11 @@ func NewKubeDockerAdapter(options *KubeDockerAdapterOptions) (*KubeDockerAdapter
return nil, fmt.Errorf("unable to initialize store backends: %w", err)
}

registrySecretStore, err := store.ConfigureRegistrySecretStore(storeOptions, options.K2DConfig.DataPath)
if err != nil {
return nil, fmt.Errorf("unable to initialize registry secret store: %w", err)
}

scheme := runtime.NewScheme()

apps.AddToScheme(scheme)
Expand All @@ -109,7 +114,7 @@ func NewKubeDockerAdapter(options *KubeDockerAdapterOptions) (*KubeDockerAdapter
configMapStore: configMapStore,
k2dServerConfiguration: options.ServerConfiguration,
logger: options.Logger,
registrySecretStore: memory.NewInMemoryStore(),
registrySecretStore: registrySecretStore,
secretStore: secretStore,
startTime: time.Now(),
}, nil
Expand Down
6 changes: 3 additions & 3 deletions internal/adapter/container_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ func (adapter *KubeDockerAdapter) getRegistryCredentials(podSpec corev1.PodSpec,

registryURL := reference.Domain(parsed)

adapter.logger.Infof("retrieving private registry credentials",
adapter.logger.Infow("retrieving private registry credentials",
"container_image", imageName,
"registry", registryURL,
)
Expand All @@ -376,12 +376,12 @@ func (adapter *KubeDockerAdapter) getRegistryCredentials(podSpec corev1.PodSpec,

registrySecret, err := adapter.registrySecretStore.GetSecret(pullSecret.Name, namespace)
if err != nil {
return "", fmt.Errorf("unable to get registry secret: %w", err)
return "", fmt.Errorf("unable to get registry secret %s: %w", pullSecret.Name, err)
}

username, password, err := k8s.GetRegistryAuthFromSecret(registrySecret, registryURL)
if err != nil {
return "", fmt.Errorf("unable to decode registry secret: %w", err)
return "", fmt.Errorf("unable to decode registry secret %s: %w", pullSecret.Name, err)
}

authConfig := registry.AuthConfig{
Expand Down
58 changes: 53 additions & 5 deletions internal/adapter/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"fmt"

"github.com/portainer/k2d/internal/adapter/store/filesystem"
"github.com/portainer/k2d/internal/adapter/store/memory"
"github.com/portainer/k2d/internal/adapter/store/volume"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
Expand All @@ -64,12 +65,13 @@ type ConfigMapStore interface {
}

// StoreOptions represents options that can be used to configure how to store ConfigMap and Secret resources.
// It is used by the ConfigureStore() function to initialize and configure the storage backend.
// It is used by the ConfigureStore() and ConfigureRegistrySecretStore() functions to initialize and configure the storage backends.
type StoreOptions struct {
Backend string
Logger *zap.SugaredLogger
Filesystem filesystem.FileSystemStoreOptions
Volume volume.VolumeStoreOptions
Backend string
RegistryBackend string
Logger *zap.SugaredLogger
Filesystem filesystem.FileSystemStoreOptions
Volume volume.VolumeStoreOptions
}

// ConfigureStore initializes and configures a storage backend for ConfigMap and Secret resources based on the provided StoreOptions.
Expand All @@ -96,15 +98,61 @@ func ConfigureStore(opts StoreOptions) (ConfigMapStore, SecretStore, error) {
return nil, nil, fmt.Errorf("failed to create filesystem store: %w", err)
}

opts.Logger.Info("Using disk store for ConfigMaps and Secrets")
return filesystemStore, filesystemStore, nil
case "volume":
opts.Volume.SecretKind = volume.SecretResourceType
volumeStore, err := volume.NewVolumeStore(opts.Logger, opts.Volume)
if err != nil {
return nil, nil, fmt.Errorf("failed to create volume store: %w", err)
}

opts.Logger.Info("Using volume store for ConfigMaps and Secrets")
return volumeStore, volumeStore, nil
default:
return nil, nil, fmt.Errorf("invalid store backend: %s", opts.Backend)
}
}

// ConfigureRegistrySecretStore initializes and configures a storage backend for Registry Secrets based on the provided StoreOptions.
// It supports multiple backends: "memory" and "volume". For the "memory" backend, it uses an in-memory store.
// The "volume" backend utilizes a volume-based store, which relies on Docker volumes and an encryption key to store encrypted data.
//
// Parameters:
// - opts: StoreOptions object containing configurations for initializing the storage backend.
// - encryptionKeyFolder: A string representing the folder where the encryption key is stored or will be generated.
//
// Returns:
// - SecretStore: An interface for interacting with Kubernetes Registry Secrets.
// - error: An error object if any errors occur during the initialization or configuration process.
//
// Errors:
// - Returns an error if it fails to generate the encryption key for the "volume" backend.
// - Returns an error if it fails to create the volume store for the "volume" backend.
// - Returns an error if an invalid registry secret store backend is provided.
func ConfigureRegistrySecretStore(opts StoreOptions, encryptionKeyFolder string) (SecretStore, error) {
switch opts.RegistryBackend {
case "memory":

opts.Logger.Info("Using memory store for registry Secrets")
return memory.NewInMemoryStore(), nil
case "volume":
encryptionKey, err := volume.GenerateOrRetrieveEncryptionKey(opts.Logger, encryptionKeyFolder)
if err != nil {
return nil, fmt.Errorf("failed to generate encryption key: %w", err)
}

opts.Volume.EncryptionKey = encryptionKey
opts.Volume.SecretKind = volume.RegistrySecretResourceType

volumeStore, err := volume.NewVolumeStore(opts.Logger, opts.Volume)
if err != nil {
return nil, fmt.Errorf("failed to create volume store: %w", err)
}

opts.Logger.Info("Using encrypted volume store for registry Secrets")
return volumeStore, nil
default:
return nil, fmt.Errorf("invalid registry secret store backend: %s", opts.RegistryBackend)
}
}
7 changes: 4 additions & 3 deletions internal/adapter/store/volume/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
"github.com/portainer/k2d/internal/adapter/errors"
"github.com/portainer/k2d/internal/adapter/types"
"github.com/portainer/k2d/pkg/maputils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -162,8 +163,8 @@ func (store *VolumeStore) StoreConfigMap(configMap *corev1.ConfigMap) error {
volumeName := buildConfigMapVolumeName(configMap.Name, configMap.Namespace)

labels := map[string]string{
ResourceTypeLabelKey: ConfigMapResourceType,
NamespaceNameLabelKey: configMap.Namespace,
ResourceTypeLabelKey: ConfigMapResourceType,
types.NamespaceLabelKey: configMap.Namespace,
}
maputils.MergeMapsInPlace(labels, configMap.Labels)

Expand All @@ -186,7 +187,7 @@ func (store *VolumeStore) StoreConfigMap(configMap *corev1.ConfigMap) error {
// createConfigMapFromVolume constructs a Kubernetes ConfigMap object from a Docker volume.
// Returns a ConfigMap object, and an error if any occurs (e.g., if the volume's creation timestamp is not parseable).
func createConfigMapFromVolume(volume *volume.Volume) (core.ConfigMap, error) {
namespace := volume.Labels[NamespaceNameLabelKey]
namespace := volume.Labels[types.NamespaceLabelKey]

configMap := core.ConfigMap{
TypeMeta: metav1.TypeMeta{
Expand Down
Loading