From 832a69b0bee6ec289521fbd59ddd480372493ee3 Mon Sep 17 00:00:00 2001 From: Ashley Cui Date: Fri, 15 Jan 2021 01:27:23 -0500 Subject: Implement Secrets Implement podman secret create, inspect, ls, rm Implement podman run/create --secret Secrets are blobs of data that are sensitive. Currently, the only secret driver supported is filedriver, which means creating a secret stores it in base64 unencrypted in a file. After creating a secret, a user can use the --secret flag to expose the secret inside the container at /run/secrets/[secretname] This secret will not be commited to an image on a podman commit Signed-off-by: Ashley Cui --- pkg/domain/entities/engine_container.go | 4 + pkg/domain/entities/secrets.go | 104 ++++++++++++++++++++++++ pkg/domain/infra/abi/secrets.go | 138 ++++++++++++++++++++++++++++++++ pkg/domain/infra/tunnel/secrets.go | 82 +++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 pkg/domain/entities/secrets.go create mode 100644 pkg/domain/infra/abi/secrets.go create mode 100644 pkg/domain/infra/tunnel/secrets.go (limited to 'pkg/domain') diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 39bda1d72..9ff1714e7 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -81,6 +81,10 @@ type ContainerEngine interface { PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error) SetupRootless(ctx context.Context, cmd *cobra.Command) error + SecretCreate(ctx context.Context, name string, reader io.Reader, options SecretCreateOptions) (*SecretCreateReport, error) + SecretInspect(ctx context.Context, nameOrIDs []string) ([]*SecretInfoReport, []error, error) + SecretList(ctx context.Context) ([]*SecretInfoReport, error) + SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) Shutdown(ctx context.Context) SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) Unshare(ctx context.Context, args []string) error diff --git a/pkg/domain/entities/secrets.go b/pkg/domain/entities/secrets.go new file mode 100644 index 000000000..3cad4c099 --- /dev/null +++ b/pkg/domain/entities/secrets.go @@ -0,0 +1,104 @@ +package entities + +import ( + "time" + + "github.com/containers/podman/v2/pkg/errorhandling" +) + +type SecretCreateReport struct { + ID string +} + +type SecretCreateOptions struct { + Driver string +} + +type SecretListRequest struct { + Filters map[string]string +} + +type SecretListReport struct { + ID string + Name string + Driver string + CreatedAt string + UpdatedAt string +} + +type SecretRmOptions struct { + All bool +} + +type SecretRmReport struct { + ID string + Err error +} + +type SecretInfoReport struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + Spec SecretSpec +} + +type SecretSpec struct { + Name string + Driver SecretDriverSpec +} + +type SecretDriverSpec struct { + Name string + Options map[string]string +} + +// swagger:model SecretCreate +type SecretCreateRequest struct { + // User-defined name of the secret. + Name string + // Base64-url-safe-encoded (RFC 4648) data to store as secret. + Data string + // Driver represents a driver (default "file") + Driver SecretDriverSpec +} + +// Secret create response +// swagger:response SecretCreateResponse +type SwagSecretCreateResponse struct { + // in:body + Body struct { + SecretCreateReport + } +} + +// Secret list response +// swagger:response SecretListResponse +type SwagSecretListResponse struct { + // in:body + Body []*SecretInfoReport +} + +// Secret inspect response +// swagger:response SecretInspectResponse +type SwagSecretInspectResponse struct { + // in:body + Body SecretInfoReport +} + +// No such secret +// swagger:response NoSuchSecret +type SwagErrNoSuchSecret struct { + // in:body + Body struct { + errorhandling.ErrorModel + } +} + +// Secret in use +// swagger:response SecretInUse +type SwagErrSecretInUse struct { + // in:body + Body struct { + errorhandling.ErrorModel + } +} diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go new file mode 100644 index 000000000..b1fe60e01 --- /dev/null +++ b/pkg/domain/infra/abi/secrets.go @@ -0,0 +1,138 @@ +package abi + +import ( + "context" + "io" + "io/ioutil" + "path/filepath" + + "github.com/containers/common/pkg/secrets" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) { + data, _ := ioutil.ReadAll(reader) + secretsPath := ic.Libpod.GetSecretsStorageDir() + manager, err := secrets.NewManager(secretsPath) + if err != nil { + return nil, err + } + driverOptions := make(map[string]string) + + if options.Driver == "" { + options.Driver = "file" + } + if options.Driver == "file" { + driverOptions["path"] = filepath.Join(secretsPath, "filedriver") + } + secretID, err := manager.Store(name, data, options.Driver, driverOptions) + if err != nil { + return nil, err + } + return &entities.SecretCreateReport{ + ID: secretID, + }, nil +} + +func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*entities.SecretInfoReport, []error, error) { + secretsPath := ic.Libpod.GetSecretsStorageDir() + manager, err := secrets.NewManager(secretsPath) + if err != nil { + return nil, nil, err + } + errs := make([]error, 0, len(nameOrIDs)) + reports := make([]*entities.SecretInfoReport, 0, len(nameOrIDs)) + for _, nameOrID := range nameOrIDs { + secret, err := manager.Lookup(nameOrID) + if err != nil { + if errors.Cause(err).Error() == "no such secret" { + errs = append(errs, err) + continue + } else { + return nil, nil, errors.Wrapf(err, "error inspecting secret %s", nameOrID) + } + } + report := &entities.SecretInfoReport{ + ID: secret.ID, + CreatedAt: secret.CreatedAt, + UpdatedAt: secret.CreatedAt, + Spec: entities.SecretSpec{ + Name: secret.Name, + Driver: entities.SecretDriverSpec{ + Name: secret.Driver, + }, + }, + } + reports = append(reports, report) + + } + + return reports, errs, nil +} + +func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) { + secretsPath := ic.Libpod.GetSecretsStorageDir() + manager, err := secrets.NewManager(secretsPath) + if err != nil { + return nil, err + } + secretList, err := manager.List() + if err != nil { + return nil, err + } + report := make([]*entities.SecretInfoReport, 0, len(secretList)) + for _, secret := range secretList { + reportItem := entities.SecretInfoReport{ + ID: secret.ID, + CreatedAt: secret.CreatedAt, + UpdatedAt: secret.CreatedAt, + Spec: entities.SecretSpec{ + Name: secret.Name, + Driver: entities.SecretDriverSpec{ + Name: secret.Driver, + Options: secret.DriverOptions, + }, + }, + } + report = append(report, &reportItem) + } + return report, nil +} + +func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, options entities.SecretRmOptions) ([]*entities.SecretRmReport, error) { + var ( + err error + toRemove []string + reports = []*entities.SecretRmReport{} + ) + secretsPath := ic.Libpod.GetSecretsStorageDir() + manager, err := secrets.NewManager(secretsPath) + if err != nil { + return nil, err + } + toRemove = nameOrIDs + if options.All { + allSecrs, err := manager.List() + if err != nil { + return nil, err + } + for _, secr := range allSecrs { + toRemove = append(toRemove, secr.ID) + } + } + for _, nameOrID := range toRemove { + deletedID, err := manager.Delete(nameOrID) + if err == nil || errors.Cause(err).Error() == "no such secret" { + reports = append(reports, &entities.SecretRmReport{ + Err: err, + ID: deletedID, + }) + continue + } else { + return nil, err + } + } + + return reports, nil +} diff --git a/pkg/domain/infra/tunnel/secrets.go b/pkg/domain/infra/tunnel/secrets.go new file mode 100644 index 000000000..f7c0f7d13 --- /dev/null +++ b/pkg/domain/infra/tunnel/secrets.go @@ -0,0 +1,82 @@ +package tunnel + +import ( + "context" + "io" + + "github.com/containers/podman/v2/pkg/bindings/secrets" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/errorhandling" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) { + opts := new(secrets.CreateOptions).WithDriver(options.Driver).WithName(name) + created, _ := secrets.Create(ic.ClientCtx, reader, opts) + return created, nil +} + +func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*entities.SecretInfoReport, []error, error) { + allInspect := make([]*entities.SecretInfoReport, 0, len(nameOrIDs)) + errs := make([]error, 0, len(nameOrIDs)) + for _, name := range nameOrIDs { + inspected, err := secrets.Inspect(ic.ClientCtx, name, nil) + if err != nil { + errModel, ok := err.(errorhandling.ErrorModel) + if !ok { + return nil, nil, err + } + if errModel.ResponseCode == 404 { + errs = append(errs, errors.Errorf("no such secret %q", name)) + continue + } + return nil, nil, err + } + allInspect = append(allInspect, inspected) + } + return allInspect, errs, nil +} + +func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) { + secrs, _ := secrets.List(ic.ClientCtx, nil) + return secrs, nil +} + +func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, options entities.SecretRmOptions) ([]*entities.SecretRmReport, error) { + allRm := make([]*entities.SecretRmReport, 0, len(nameOrIDs)) + if options.All { + allSecrets, err := secrets.List(ic.ClientCtx, nil) + if err != nil { + return nil, err + } + for _, secret := range allSecrets { + allRm = append(allRm, &entities.SecretRmReport{ + Err: secrets.Remove(ic.ClientCtx, secret.ID), + ID: secret.ID, + }) + } + return allRm, nil + } + for _, name := range nameOrIDs { + secret, err := secrets.Inspect(ic.ClientCtx, name, nil) + if err != nil { + errModel, ok := err.(errorhandling.ErrorModel) + if !ok { + return nil, err + } + if errModel.ResponseCode == 404 { + allRm = append(allRm, &entities.SecretRmReport{ + Err: errors.Errorf("no secret with name or id %q: no such secret ", name), + ID: "", + }) + continue + } + } + allRm = append(allRm, &entities.SecretRmReport{ + Err: secrets.Remove(ic.ClientCtx, name), + ID: secret.ID, + }) + + } + return allRm, nil +} -- cgit v1.2.3-54-g00ecf