aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorcdoern <cdoern@redhat.com>2022-06-07 16:37:59 -0400
committerCharlie Doern <cdoern@redhat.com>2022-07-20 12:55:33 -0400
commit438fef1caa26a1f09cd7be341e854d4bd5726c2b (patch)
treee449aceae653fcce93fe38aa38a60d08a95bda1e /pkg
parent8c9eff5b12eebe318be1f905562be6a236285caa (diff)
downloadpodman-438fef1caa26a1f09cd7be341e854d4bd5726c2b.tar.gz
podman-438fef1caa26a1f09cd7be341e854d4bd5726c2b.tar.bz2
podman-438fef1caa26a1f09cd7be341e854d4bd5726c2b.zip
kube secret handling for podman play kube
add support for both creating a secret using yaml and mounting a secret as a volume given a yaml file. Kubernetes secrets have a different structure than podman and therefore have to be handeled differently. In this PR, I have introduced the basic usecases of kube secrets with more implementations like env secrets to come! resolves #12396 Signed-off-by: Charlie Doern <cdoern@redhat.com>
Diffstat (limited to 'pkg')
-rw-r--r--pkg/domain/entities/play.go5
-rw-r--r--pkg/domain/infra/abi/play.go80
-rw-r--r--pkg/domain/infra/abi/secrets.go2
-rw-r--r--pkg/k8s.io/api/core/v1/types.go1
-rw-r--r--pkg/specgen/generate/kube/kube.go10
-rw-r--r--pkg/specgen/generate/kube/play_test.go2
-rw-r--r--pkg/specgen/generate/kube/volume.go56
7 files changed, 144 insertions, 12 deletions
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index 35a5d8a4a..5bb438d7d 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -88,6 +88,7 @@ type PlayKubeReport struct {
// Volumes - volumes created by play kube.
Volumes []PlayKubeVolume
PlayKubeTeardown
+ Secrets []PlaySecret
}
type KubePlayReport = PlayKubeReport
@@ -100,3 +101,7 @@ type PlayKubeTeardown struct {
StopReport []*PodStopReport
RmReport []*PodRmReport
}
+
+type PlaySecret struct {
+ CreateReport *SecretCreateReport
+}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index c913fdb69..116604035 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -84,15 +84,15 @@ func (ic *ContainerEngine) createServiceContainer(ctx context.Context, name stri
return ctr, nil
}
-// Creates the name for a service container based on the provided content of a
-// K8s yaml file.
-func serviceContainerName(content []byte) string {
+// Creates the name for a k8s entity based on the provided content of a
+// K8s yaml file and a given suffix.
+func k8sName(content []byte, suffix string) string {
// The name of the service container is the first 12
// characters of the yaml file's hash followed by the
// '-service' suffix to guarantee a predictable and
// discoverable name.
hash := digest.FromBytes(content).Encoded()
- return hash[0:12] + "-service"
+ return hash[0:12] + "-" + suffix
}
func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options entities.PlayKubeOptions) (_ *entities.PlayKubeReport, finalErr error) {
@@ -132,7 +132,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
// TODO: create constants for the various "kinds" of yaml files.
var serviceContainer *libpod.Container
if options.ServiceContainer && (kind == "Pod" || kind == "Deployment") {
- ctr, err := ic.createServiceContainer(ctx, serviceContainerName(content), options)
+ ctr, err := ic.createServiceContainer(ctx, k8sName(content, "service"), options)
if err != nil {
return nil, err
}
@@ -213,6 +213,19 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
return nil, fmt.Errorf("unable to read YAML as Kube ConfigMap: %w", err)
}
configMaps = append(configMaps, configMap)
+ case "Secret":
+ var secret v1.Secret
+
+ if err := yaml.Unmarshal(document, &secret); err != nil {
+ return nil, fmt.Errorf("unable to read YAML as kube secret: %w", err)
+ }
+
+ r, err := ic.playKubeSecret(&secret)
+ if err != nil {
+ return nil, err
+ }
+ report.Secrets = append(report.Secrets, entities.PlaySecret{CreateReport: r})
+ validKinds++
default:
logrus.Infof("Kube kind %s not supported", kind)
continue
@@ -380,7 +393,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
configMaps = append(configMaps, cm)
}
- volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps)
+ volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps, secretsManager)
if err != nil {
return nil, err
}
@@ -388,7 +401,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
// Go through the volumes and create a podman volume for all volumes that have been
// defined by a configmap
for _, v := range volumes {
- if v.Type == kube.KubeVolumeTypeConfigMap && !v.Optional {
+ if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional {
vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source))
if err != nil {
if errors.Is(err, define.ErrVolumeExists) {
@@ -579,6 +592,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
UserNSIsHost: p.Userns.IsHost(),
Volumes: volumes,
}
+
specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
if err != nil {
return nil, err
@@ -964,3 +978,55 @@ func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ e
return reports, nil
}
+
+// playKubeSecret allows users to create and store a kubernetes secret as a podman secret
+func (ic *ContainerEngine) playKubeSecret(secret *v1.Secret) (*entities.SecretCreateReport, error) {
+ r := &entities.SecretCreateReport{}
+
+ // Create the secret manager before hand
+ secretsManager, err := ic.Libpod.SecretsManager()
+ if err != nil {
+ return nil, err
+ }
+
+ data, err := yaml.Marshal(secret)
+ if err != nil {
+ return nil, err
+ }
+
+ secretsPath := ic.Libpod.GetSecretsStorageDir()
+ opts := make(map[string]string)
+ opts["path"] = filepath.Join(secretsPath, "filedriver")
+ // maybe k8sName(data)...
+ // using this does not allow the user to use the name given to the secret
+ // but keeping secret.Name as the ID can lead to a collision.
+
+ s, err := secretsManager.Lookup(secret.Name)
+ if err == nil {
+ if val, ok := s.Metadata["immutable"]; ok {
+ if val == "true" {
+ return nil, fmt.Errorf("cannot remove colliding secret as it is set to immutable")
+ }
+ }
+ _, err = secretsManager.Delete(s.Name)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // now we have either removed the old secret w/ the same name or
+ // the name was not taken. Either way, we can now store.
+
+ meta := make(map[string]string)
+ if secret.Immutable != nil && *secret.Immutable {
+ meta["immutable"] = "true"
+ }
+ secretID, err := secretsManager.Store(secret.Name, data, "file", opts, meta)
+ if err != nil {
+ return nil, err
+ }
+
+ r.ID = secretID
+
+ return r, nil
+}
diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go
index 7321ef715..e82fa4fdd 100644
--- a/pkg/domain/infra/abi/secrets.go
+++ b/pkg/domain/infra/abi/secrets.go
@@ -42,7 +42,7 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader
}
}
- secretID, err := manager.Store(name, data, options.Driver, options.DriverOpts)
+ secretID, err := manager.Store(name, data, options.Driver, options.DriverOpts, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go
index 48f353cc6..39a675dae 100644
--- a/pkg/k8s.io/api/core/v1/types.go
+++ b/pkg/k8s.io/api/core/v1/types.go
@@ -56,6 +56,7 @@ type VolumeSource struct {
// ConfigMap represents a configMap that should populate this volume
// +optional
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
+ Secret *SecretVolumeSource
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go
index 454a1e1d0..e9abf419b 100644
--- a/pkg/specgen/generate/kube/kube.go
+++ b/pkg/specgen/generate/kube/kube.go
@@ -398,6 +398,16 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Type: "b",
}
s.Devices = append(s.Devices, device)
+ case KubeVolumeTypeSecret:
+ // in podman play kube we need to add these secrets as volumes rather than as
+ // specgen.Secrets. Adding them as volumes allows for all key: value pairs to be mounted
+ secretVolume := specgen.NamedVolume{
+ Dest: volume.MountPath,
+ Name: volumeSource.Source,
+ Options: options,
+ }
+
+ s.Volumes = append(s.Volumes, &secretVolume)
default:
return nil, errors.New("unsupported volume source type")
}
diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go
index 466dab610..470c0c39c 100644
--- a/pkg/specgen/generate/kube/play_test.go
+++ b/pkg/specgen/generate/kube/play_test.go
@@ -28,7 +28,7 @@ func createSecrets(t *testing.T, d string) *secrets.SecretsManager {
data, err := json.Marshal(s.Data)
assert.NoError(t, err)
- _, err = secretsManager.Store(s.ObjectMeta.Name, data, driver, driverOpts)
+ _, err = secretsManager.Store(s.ObjectMeta.Name, data, driver, driverOpts, nil)
assert.NoError(t, err)
}
diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go
index f5c0c241d..c12adadd8 100644
--- a/pkg/specgen/generate/kube/volume.go
+++ b/pkg/specgen/generate/kube/volume.go
@@ -6,9 +6,13 @@ import (
"os"
"github.com/containers/common/pkg/parse"
+ "github.com/containers/common/pkg/secrets"
"github.com/containers/podman/v4/libpod"
v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
+ metav1 "github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/apis/meta/v1"
+
"github.com/sirupsen/logrus"
+ "gopkg.in/yaml.v3"
)
const (
@@ -27,6 +31,7 @@ const (
KubeVolumeTypeConfigMap
KubeVolumeTypeBlockDevice
KubeVolumeTypeCharDevice
+ KubeVolumeTypeSecret
)
//nolint:revive
@@ -125,6 +130,49 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error)
}, nil
}
+// VolumeFromSecret creates a new kube volume from a kube secret.
+func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secrets.SecretsManager) (*KubeVolume, error) {
+ // returns a byte array of a kube secret data, meaning this needs to go into a string map
+ _, secretByte, err := secretsManager.LookupSecretData(secretSource.SecretName)
+ if err != nil {
+ return nil, err
+ }
+
+ // unmarshaling directly into a v1.secret creates type mismatch errors
+ // use a more friendly, string only secret struct.
+ type KubeSecret struct {
+ metav1.TypeMeta `json:",inline"`
+ // +optional
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+ // +optional
+ Immutable *bool `json:"immutable,omitempty"`
+ Data map[string]string `json:"data,omitempty"`
+ // +optional
+ StringData map[string]string `json:"stringData,omitempty"`
+ // +optional
+ Type string `json:"type,omitempty"`
+ }
+
+ data := &KubeSecret{}
+
+ err = yaml.Unmarshal(secretByte, data)
+ if err != nil {
+ return nil, err
+ }
+
+ kv := &KubeVolume{}
+ kv.Type = KubeVolumeTypeSecret
+ kv.Source = secretSource.SecretName
+ kv.Optional = *secretSource.Optional
+ kv.Items = make(map[string]string)
+
+ // add key: value pairs to the items array
+ for key, entry := range data.Data {
+ kv.Items[key] = entry
+ }
+ return kv, nil
+}
+
// Create a KubeVolume from a PersistentVolumeClaimVolumeSource
func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource) (*KubeVolume, error) {
return &KubeVolume{
@@ -172,7 +220,7 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config
}
// Create a KubeVolume from one of the supported VolumeSource
-func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) {
+func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (*KubeVolume, error) {
switch {
case volumeSource.HostPath != nil:
return VolumeFromHostPath(volumeSource.HostPath)
@@ -180,17 +228,19 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap) (
return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim)
case volumeSource.ConfigMap != nil:
return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps)
+ case volumeSource.Secret != nil:
+ return VolumeFromSecret(volumeSource.Secret, secretsManager)
default:
return nil, errors.New("HostPath, ConfigMap, and PersistentVolumeClaim are currently the only supported VolumeSource")
}
}
// Create a map of volume name to KubeVolume
-func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap) (map[string]*KubeVolume, error) {
+func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (map[string]*KubeVolume, error) {
volumes := make(map[string]*KubeVolume)
for _, specVolume := range specVolumes {
- volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps)
+ volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager)
if err != nil {
return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err)
}