-
Notifications
You must be signed in to change notification settings - Fork 89
/
engine_linux.go
177 lines (162 loc) · 7.02 KB
/
engine_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Copyright (c) 2019, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package engine
import (
"context"
"encoding/json"
"fmt"
"net"
"net/rpc"
"os"
"syscall"
"github.com/sylabs/singularity/v4/internal/pkg/runtime/engine/config/starter"
"github.com/sylabs/singularity/v4/pkg/runtime/engine/config"
)
// Engine is the combination of an Operations and a config.Common. The singularity
// startup routines (internal/app/starter/*) can spawn a container process from this type.
type Engine struct {
Operations
*config.Common
}
// Operations is an interface describing necessary operations to launch
// a container process. Some of them may be called with elevated privilege
// or the potential to escalate privileges. Refer to an individual method
// documentation for a detailed description of the context in which it is called.
type Operations interface {
// Config returns a zero value of the current EngineConfig, which
// depends on the implementation, used to populate the Common struct.
//
// Since this method simply returns a zero value of the concrete
// EngineConfig, it does not matter whether or not there are any elevated
// privileges during this call.
Config() config.EngineConfig
// InitConfig stores the parsed config.Common inside the Operations
// implementation.
//
// Since this method simply stores config.Common, it does not matter
// whether or not there are any elevated privileges during this call.
InitConfig(*config.Common)
// PrepareConfig is called during stage1 to validate and prepare
// container configuration.
//
// No additional privileges can be gained as any of them are already
// dropped by the time PrepareConfig is called.
PrepareConfig(*starter.Config) error
// CreateContainer is called from master process to prepare container
// environment, e.g. perform mount operations, setup network, etc.
//
// Additional privileges required for setup may be gained when running
// in suid flow. However, when a user namespace is requested and it is not
// a hybrid workflow (e.g. fakeroot), then there is no privileged saved uid
// and thus no additional privileges can be gained.
CreateContainer(context.Context, int, net.Conn) error
// StartProcess is called during stage2 after RPC server finished
// environment preparation. This is the container process itself.
//
// No additional privileges can be gained during this call (unless container
// is executed as root intentionally) as starter will set uid/euid/suid
// to the targetUID (PrepareConfig will set it by calling starter.Config.SetTargetUID).
StartProcess(net.Conn) error
// PostStartProcess is called from master after successful
// execution of the container process.
//
// Additional privileges may be gained when running
// in suid flow. However, when a user namespace is requested and it is not
// a hybrid workflow (e.g. fakeroot), then there is no privileged saved uid
// and thus no additional privileges can be gained.
PostStartProcess(context.Context, int) error
// MonitorContainer is called from master once the container has
// been spawned. It will typically block until the container exists.
//
// Additional privileges may be gained when running
// in suid flow. However, when a user namespace is requested and it is not
// a hybrid workflow (e.g. fakeroot), then there is no privileged saved uid
// and thus no additional privileges can be gained.
MonitorContainer(int, chan os.Signal) (syscall.WaitStatus, error)
// CleanupContainer is called from master after the MonitorContainer returns.
// It is responsible for ensuring that the container has been properly torn down.
//
// Additional privileges may be gained when running
// in suid flow. However, when a user namespace is requested and it is not
// a hybrid workflow (e.g. fakeroot), then there is no privileged saved uid
// and thus no additional privileges can be gained.
CleanupContainer(context.Context, error, syscall.WaitStatus) error
// PostStartHost is called after the container process has been executed. It
// is run in the POST_START_HOST process forked from starter before
// namespace setup etc. and will perform any cleanup in the host mount
// namespace at time of CLI execution.
//
// No additional privileges can be gained during this call in the setuid
// flow as privileges are dropped permanently after forking in starter.
PostStartHost(context.Context) error
// CleanupHost is called on container exit or startup failure to perform any
// required cleanup in the host mount namespace at time of CLI execution.
//
// If container creation fails early, in STAGE 1, it will be called directly
// from STAGE 1. Otherwise it is run in the CLEANUP_HOST PROCESS, triggered
// by master, or the SIGKILL parent death signal.
//
// No additional privileges can be gained during this call in the setuid
// flow, as privileges are dropped permanently in both STAGE1 and
// CLEANUP_HOST after forking in starter.
CleanupHost(context.Context) error
}
// getName returns the engine name set in JSON []byte configuration.
func getName(b []byte) string {
engineName := struct {
EngineName string `json:"engineName"`
}{}
if err := json.Unmarshal(b, &engineName); err != nil {
return ""
}
return engineName.EngineName
}
// Get returns the engine described by the JSON []byte configuration.
func Get(b []byte) (*Engine, error) {
engineName := getName(b)
// ensure engine with given name is registered
eOp, ok := registeredOperations[engineName]
if !ok {
return nil, fmt.Errorf("engine %q is not found", engineName)
}
// create empty Engine object with properly initialized EngineConfig && Operations
e := &Engine{
Operations: eOp,
Common: &config.Common{
EngineConfig: eOp.Config(),
},
}
// parse received JSON configuration to specific EngineConfig
if err := json.Unmarshal(b, e.Common); err != nil {
return nil, fmt.Errorf("could not parse JSON configuration: %s", err)
}
e.InitConfig(e.Common)
return e, nil
}
var (
// registeredOperations contains a map relating an Engine name to a set
// of operations provided by an engine.
registeredOperations = make(map[string]Operations)
// registerEngineRPCMethods contains a map relating an Engine name to a set
// of RPC methods served by RPC server.
registeredRPCMethods = make(map[string]interface{})
)
// ServeRPCRequests serves runtime engine RPC requests with
// corresponding registered engine methods.
func ServeRPCRequests(e *Engine, conn net.Conn) {
methods, ok := registeredRPCMethods[e.EngineName]
if ok {
rpc.RegisterName(e.EngineName, methods)
rpc.ServeConn(conn)
}
}
// RegisterOperations registers engine operations for a runtime engine.
func RegisterOperations(name string, operations Operations) {
registeredOperations[name] = operations
}
// RegisterRPCMethods registers engine RPC methods served by RPC server.
func RegisterRPCMethods(name string, methods interface{}) {
registeredRPCMethods[name] = methods
}