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

remote: exec: do not leak session IDs on errors #20405

Merged
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
27 changes: 27 additions & 0 deletions pkg/api/handlers/compat/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,30 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
}
logrus.Debugf("Attach for container %s exec session %s completed successfully", sessionCtr.ID(), sessionID)
}

// ExecRemoveHandler removes a exec session.
func ExecRemoveHandler(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)

sessionID := mux.Vars(r)["id"]

bodyParams := new(handlers.ExecRemoveConfig)

if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to decode parameters for %s: %w", r.URL.String(), err))
return
}

sessionCtr, err := runtime.GetExecSessionContainer(sessionID)
if err != nil {
utils.Error(w, http.StatusNotFound, err)
return
}

logrus.Debugf("Removing exec session %s of container %s", sessionID, sessionCtr.ID())
if err := sessionCtr.ExecRemove(sessionID, bodyParams.Force); err != nil {
utils.InternalServerError(w, err)
return
}
logrus.Debugf("Removing exec session %s for container %s completed successfully", sessionID, sessionCtr.ID())
}
4 changes: 4 additions & 0 deletions pkg/api/handlers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,7 @@ type ExecStartConfig struct {
Height uint16 `json:"h"`
Width uint16 `json:"w"`
}

type ExecRemoveConfig struct {
Force bool `json:"Force"`
}
34 changes: 34 additions & 0 deletions pkg/api/server/register_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,5 +346,39 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/internalError"
r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet)
// ................. .... ........................ ...... ExecRemoveLibpod
// ---
// tags:
// - exec
// summary: Remove an exec instance
// description: |
// Remove a previously set up exec instance. If force is true, the exec session is killed if it is still running.
// parameters:
// - in: path
// name: id
// type: string
// required: true
// description: Exec instance ID
// - in: body
// name: control
// description: Attributes for removal
// schema:
// type: object
// properties:
// Force:
// type: boolean
// description: Force removal of the session.
// produces:
// - application/json
// responses:
// 200:
// description: no error
// 404:
// $ref: "#/responses/execSessionNotFound"
// 409:
// description: container is not running.
// 500:
// $ref: "#/responses/internalError"
r.Handle(VersionedPath("/libpod/exec/{id}/remove"), s.APIHandler(compat.ExecRemoveHandler)).Methods(http.MethodPost)
return nil
}
38 changes: 38 additions & 0 deletions pkg/bindings/containers/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,41 @@ func ExecStart(ctx context.Context, sessionID string, options *ExecStartOptions)

return resp.Process(nil)
}

// ExecRemove removes a given exec session.
func ExecRemove(ctx context.Context, sessionID string, options *ExecRemoveOptions) error {
if options == nil {
options = new(ExecRemoveOptions)
}
_ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}

logrus.Debugf("Removing exec session ID %q", sessionID)

// We force Detach to true
body := struct {
Force bool `json:"Force"`
}{
Force: false,
}

if options.Force != nil {
body.Force = *options.Force
}

bodyJSON, err := json.Marshal(body)
if err != nil {
return err
}

resp, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/remove", nil, nil, sessionID)
if err != nil {
return err
}
defer resp.Body.Close()

return resp.Process(nil)
}
7 changes: 7 additions & 0 deletions pkg/bindings/containers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,10 @@ type CopyOptions struct {
// by the other type.
NoOverwriteDirNonDir *bool
}

// ExecRemoveOptions are optional options for removing an exec session
//
//go:generate go run ../generator/generator.go ExecRemoveOptions
type ExecRemoveOptions struct {
Force *bool
}
33 changes: 33 additions & 0 deletions pkg/bindings/containers/types_execremove_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,13 +579,21 @@ func makeExecConfig(options entities.ExecOptions) *handlers.ExecCreateConfig {
return createConfig
}

func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, options entities.ExecOptions, streams define.AttachStreams) (int, error) {
func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, options entities.ExecOptions, streams define.AttachStreams) (exitCode int, retErr error) {
createConfig := makeExecConfig(options)

sessionID, err := containers.ExecCreate(ic.ClientCtx, nameOrID, createConfig)
if err != nil {
return 125, err
}
defer func() {
if err := containers.ExecRemove(ic.ClientCtx, sessionID, nil); err != nil {
if retErr == nil {
exitCode = -1
retErr = err
}
}
}()
startAndAttachOptions := new(containers.ExecStartAndAttachOptions)
startAndAttachOptions.WithOutputStream(streams.OutputStream).WithErrorStream(streams.ErrorStream)
if streams.InputStream != nil {
Expand All @@ -604,13 +612,18 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, o
return inspectOut.ExitCode, nil
}

func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) {
func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (retSessionID string, retErr error) {
createConfig := makeExecConfig(options)

sessionID, err := containers.ExecCreate(ic.ClientCtx, nameOrID, createConfig)
if err != nil {
return "", err
}
defer func() {
if retErr != nil {
_ = containers.ExecRemove(ic.ClientCtx, sessionID, nil)
}
}()
Comment on lines +622 to +626
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be handled on the server side? We should not expect all API clients to know about this.


if err := containers.ExecStart(ic.ClientCtx, sessionID, nil); err != nil {
return "", err
Expand Down
1 change: 0 additions & 1 deletion test/system/075-exec.bats
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ load helpers
}

@test "podman exec - does not leak session IDs on invalid command" {
skip_if_remote "FIXME FIXME FIXME: this should work on remote, but does not"
run_podman run -d $IMAGE top
cid="$output"

Expand Down