diff options
author | Ashley Cui <acui@redhat.com> | 2021-01-15 01:27:23 -0500 |
---|---|---|
committer | Ashley Cui <acui@redhat.com> | 2021-02-09 09:13:21 -0500 |
commit | 832a69b0bee6ec289521fbd59ddd480372493ee3 (patch) | |
tree | 4c8a14b7fad879dc454c37f8b59120cf74ceafd1 /pkg/api | |
parent | 2aaf631586e82192e6b7b992e6b5c8717eb792d7 (diff) | |
download | podman-832a69b0bee6ec289521fbd59ddd480372493ee3.tar.gz podman-832a69b0bee6ec289521fbd59ddd480372493ee3.tar.bz2 podman-832a69b0bee6ec289521fbd59ddd480372493ee3.zip |
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 <acui@redhat.com>
Diffstat (limited to 'pkg/api')
-rw-r--r-- | pkg/api/handlers/compat/secrets.go | 121 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/secrets.go | 40 | ||||
-rw-r--r-- | pkg/api/handlers/utils/errors.go | 8 | ||||
-rw-r--r-- | pkg/api/server/register_secrets.go | 194 | ||||
-rw-r--r-- | pkg/api/server/server.go | 1 | ||||
-rw-r--r-- | pkg/api/tags.yaml | 4 |
6 files changed, 368 insertions, 0 deletions
diff --git a/pkg/api/handlers/compat/secrets.go b/pkg/api/handlers/compat/secrets.go new file mode 100644 index 000000000..ea2dfc707 --- /dev/null +++ b/pkg/api/handlers/compat/secrets.go @@ -0,0 +1,121 @@ +package compat + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + + "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/pkg/api/handlers/utils" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/infra/abi" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func ListSecrets(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + Filters map[string][]string `schema:"filters"` + }{} + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + if len(query.Filters) > 0 { + utils.Error(w, "filters not supported", http.StatusBadRequest, errors.New("bad parameter")) + } + ic := abi.ContainerEngine{Libpod: runtime} + reports, err := ic.SecretList(r.Context()) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, reports) +} + +func InspectSecret(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + ) + name := utils.GetName(r) + names := []string{name} + ic := abi.ContainerEngine{Libpod: runtime} + reports, errs, err := ic.SecretInspect(r.Context(), names) + if err != nil { + utils.InternalServerError(w, err) + return + } + if len(errs) > 0 { + utils.SecretNotFound(w, name, errs[0]) + return + } + utils.WriteResponse(w, http.StatusOK, reports[0]) + +} + +func RemoveSecret(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + ) + + opts := entities.SecretRmOptions{} + name := utils.GetName(r) + ic := abi.ContainerEngine{Libpod: runtime} + reports, err := ic.SecretRm(r.Context(), []string{name}, opts) + if err != nil { + utils.InternalServerError(w, err) + return + } + if reports[0].Err != nil { + utils.SecretNotFound(w, name, reports[0].Err) + return + } + utils.WriteResponse(w, http.StatusNoContent, nil) +} + +func CreateSecret(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + ) + opts := entities.SecretCreateOptions{} + createParams := struct { + *entities.SecretCreateRequest + Labels map[string]string `schema:"labels"` + }{} + + if err := json.NewDecoder(r.Body).Decode(&createParams); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + if len(createParams.Labels) > 0 { + utils.Error(w, "labels not supported", http.StatusBadRequest, errors.New("bad parameter")) + } + + decoded, _ := base64.StdEncoding.DecodeString(createParams.Data) + reader := bytes.NewReader(decoded) + opts.Driver = createParams.Driver.Name + + ic := abi.ContainerEngine{Libpod: runtime} + report, err := ic.SecretCreate(r.Context(), createParams.Name, reader, opts) + if err != nil { + if errors.Cause(err).Error() == "secret name in use" { + utils.Error(w, "name conflicts with an existing object", http.StatusConflict, err) + return + } + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, report) +} + +func UpdateSecret(w http.ResponseWriter, r *http.Request) { + utils.Error(w, fmt.Sprintf("unsupported endpoint: %v", r.Method), http.StatusNotImplemented, errors.New("update is not supported")) +} diff --git a/pkg/api/handlers/libpod/secrets.go b/pkg/api/handlers/libpod/secrets.go new file mode 100644 index 000000000..447a5d021 --- /dev/null +++ b/pkg/api/handlers/libpod/secrets.go @@ -0,0 +1,40 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/pkg/api/handlers/utils" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/infra/abi" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func CreateSecret(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + Name string `schema:"name"` + Driver string `schema:"driver"` + }{ + // override any golang type defaults + } + opts := entities.SecretCreateOptions{} + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + opts.Driver = query.Driver + + ic := abi.ContainerEngine{Libpod: runtime} + report, err := ic.SecretCreate(r.Context(), query.Name, r.Body, opts) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, report) +} diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index e2c287c45..c8785fb89 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -80,6 +80,14 @@ func SessionNotFound(w http.ResponseWriter, name string, err error) { Error(w, msg, http.StatusNotFound, err) } +func SecretNotFound(w http.ResponseWriter, nameOrID string, err error) { + if errors.Cause(err).Error() != "no such secret" { + InternalServerError(w, err) + } + msg := fmt.Sprintf("No such secret: %s", nameOrID) + Error(w, msg, http.StatusNotFound, err) +} + func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) { msg := fmt.Sprintf("Container %s is not running", containerID) Error(w, msg, http.StatusConflict, err) diff --git a/pkg/api/server/register_secrets.go b/pkg/api/server/register_secrets.go new file mode 100644 index 000000000..95abf83e8 --- /dev/null +++ b/pkg/api/server/register_secrets.go @@ -0,0 +1,194 @@ +package server + +import ( + "net/http" + + "github.com/containers/podman/v2/pkg/api/handlers/compat" + "github.com/containers/podman/v2/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerSecretHandlers(r *mux.Router) error { + // swagger:operation POST /libpod/secrets/create libpod libpodCreateSecret + // --- + // tags: + // - secrets + // summary: Create a secret + // parameters: + // - in: query + // name: name + // type: string + // description: User-defined name of the secret. + // required: true + // - in: query + // name: driver + // type: string + // description: Secret driver + // default: "file" + // - in: body + // name: request + // description: Secret + // schema: + // type: string + // produces: + // - application/json + // responses: + // '201': + // $ref: "#/responses/SecretCreateResponse" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/secrets/create"), s.APIHandler(libpod.CreateSecret)).Methods(http.MethodPost) + // swagger:operation GET /libpod/secrets/json libpod libpodListSecret + // --- + // tags: + // - secrets + // summary: List secrets + // description: Returns a list of secrets + // produces: + // - application/json + // parameters: + // responses: + // '200': + // "$ref": "#/responses/SecretListResponse" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/secrets/json"), s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet) + // swagger:operation GET /libpod/secrets/{name}/json libpod libpodInspectSecret + // --- + // tags: + // - secrets + // summary: Inspect secret + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the secret + // produces: + // - application/json + // responses: + // '200': + // "$ref": "#/responses/SecretInspectResponse" + // '404': + // "$ref": "#/responses/NoSuchSecret" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/secrets/{name}/json"), s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/secrets/{name} libpod libpodRemoveSecret + // --- + // tags: + // - secrets + // summary: Remove secret + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the secret + // - in: query + // name: all + // type: boolean + // description: Remove all secrets + // default: false + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchSecret" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/secrets/{name}"), s.APIHandler(compat.RemoveSecret)).Methods(http.MethodDelete) + + /* + * Docker compatibility endpoints + */ + // swagger:operation GET /secrets compat ListSecret + // --- + // tags: + // - secrets (compat) + // summary: List secrets + // description: Returns a list of secrets + // produces: + // - application/json + // parameters: + // responses: + // '200': + // "$ref": "#/responses/SecretListResponse" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/secrets"), s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet) + r.Handle("/secrets", s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet) + // swagger:operation POST /secrets/create compat CreateSecret + // --- + // tags: + // - secrets (compat) + // summary: Create a secret + // parameters: + // - in: body + // name: create + // description: | + // attributes for creating a secret + // schema: + // $ref: "#/definitions/SecretCreate" + // produces: + // - application/json + // responses: + // '201': + // $ref: "#/responses/SecretCreateResponse" + // '409': + // "$ref": "#/responses/SecretInUse" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/secrets/create"), s.APIHandler(compat.CreateSecret)).Methods(http.MethodPost) + r.Handle("/secrets/create", s.APIHandler(compat.CreateSecret)).Methods(http.MethodPost) + // swagger:operation GET /secrets/{name} compat InspectSecret + // --- + // tags: + // - secrets (compat) + // summary: Inspect secret + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the secret + // produces: + // - application/json + // responses: + // '200': + // "$ref": "#/responses/SecretInspectResponse" + // '404': + // "$ref": "#/responses/NoSuchSecret" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/secrets/{name}"), s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet) + r.Handle("/secrets/{name}", s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet) + // swagger:operation DELETE /secrets/{name} compat RemoveSecret + // --- + // tags: + // - secrets (compat) + // summary: Remove secret + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the secret + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchSecret" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle(VersionedPath("/secrets/{name}"), s.APIHandler(compat.RemoveSecret)).Methods(http.MethodDelete) + r.Handle("/secret/{name}", s.APIHandler(compat.RemoveSecret)).Methods(http.MethodDelete) + + r.Handle(VersionedPath("/secrets/{name}/update"), s.APIHandler(compat.UpdateSecret)).Methods(http.MethodPost) + r.Handle("/secrets/{name}/update", s.APIHandler(compat.UpdateSecret)).Methods(http.MethodPost) + return nil +} diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index d612041f6..6926eda62 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -124,6 +124,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li server.registerPlayHandlers, server.registerPluginsHandlers, server.registerPodsHandlers, + server.registerSecretHandlers, server.RegisterSwaggerHandlers, server.registerSwarmHandlers, server.registerSystemHandlers, diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml index 0cfb3f440..bb56098eb 100644 --- a/pkg/api/tags.yaml +++ b/pkg/api/tags.yaml @@ -13,6 +13,8 @@ tags: description: Actions related to pods - name: volumes description: Actions related to volumes + - name: secrets + description: Actions related to secrets - name: system description: Actions related to Podman engine - name: containers (compat) @@ -25,5 +27,7 @@ tags: description: Actions related to compatibility networks - name: volumes (compat) description: Actions related to volumes for the compatibility endpoints + - name: secrets (compat) + description: Actions related to secrets for the compatibility endpoints - name: system (compat) description: Actions related to Podman and compatibility engines |