moby-derp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,231 @@
1
+ This tool is designed to securely manage a group of related containers, known
2
+ colloquially as a ["pod"](https://kubernetes.io/docs/concepts/workloads/pods/pod/),
3
+ under the Moby container management system.
4
+
5
+ It has no aspirations to be a fully-fledged multi-host container orchestation system;
6
+ instead, it is simply a means to create and maintain a pod of containers.
7
+
8
+ The most common use-case for `moby-derp` is to allow unprivileged users to
9
+ define a pod, and then allow those users to execute `moby-derp` as a privileged
10
+ user via `sudo`. Since this removes the need for random users to have direct
11
+ control over the moby daemon, a lot of potential [privilege escalation
12
+ attacks](https://fosterelli.co/privilege-escalation-via-docker.html)
13
+ facilitated by moby's security model are thwarted.
14
+
15
+
16
+ # Installation
17
+
18
+ It's a gem:
19
+
20
+ gem install moby-derp
21
+
22
+ If you're the sturdy type that likes to run from git:
23
+
24
+ rake install
25
+
26
+ Or, if you've eschewed the convenience of Rubygems entirely, then you
27
+ presumably know what to do already.
28
+
29
+
30
+ # Usage
31
+
32
+ The main interface for `moby-derp` is the command-line tool of the same name.
33
+ It takes as its sole argument a YAML file containing a whole pile of information
34
+ about the pod and how you want the containers within it to be run. A very simple
35
+ example file might look like this:
36
+
37
+ publish:
38
+ - 80:80
39
+ containers:
40
+ nginx:
41
+ image: 'nginx:latest'
42
+ mounts:
43
+ - source: nginx
44
+ target: /etc/nginx
45
+ - source: content
46
+ target: /var/www
47
+ content_puller:
48
+ image: 'example/content_puller'
49
+ mounts:
50
+ - source: content
51
+ target: /puller
52
+
53
+ For full details on exactly what can be done using the pod configuration file,
54
+ please see [`example.yml`](example.yml), which is a heavily-commented example
55
+ of every possible configuration option that can be set.
56
+
57
+ Once you have a pod config, saved in, say, `my-pod.yml`, you can tell `moby-derp`
58
+ to run it:
59
+
60
+ moby-derp ./my-pod.yml
61
+
62
+ Often, however, you won't have the ability to contact the moby daemon directly,
63
+ so you'll run `moby-derp` via `sudo`:
64
+
65
+ sudo moby-derp ./my-pod.yml
66
+
67
+ Where this comes in handy is that `sudo` can be configured to restrict the
68
+ set of commands that a given user is allowed to run. So, for instance, if the
69
+ user who wants to run this pod is named `bob` on the system, I can put this
70
+ in my `/etc/sudoers`:
71
+
72
+ bob ALL=(root) NOPASSWD: /usr/local/bin/moby-derp */my-pod.yml
73
+
74
+ Then `bob` (and *only* `bob`) can run
75
+
76
+ sudo moby-derp ./my-pod.yml
77
+
78
+ The name of the pod is taken from the name of the file, with any extension
79
+ removed. This is used as the prefix for the name of all containers in the pod.
80
+
81
+
82
+ ## System Configuration
83
+
84
+ Some aspects of `moby-derp`'s operation are security-sensitive, and thus shouldn't
85
+ be able to be modified by the ordinary user. There is a
86
+ system-wide configuration file for this purpose, by default located at
87
+ `/etc/moby-derp.yml`.
88
+
89
+ Its structure is quite simple. A full example looks like this:
90
+
91
+ mount_root: '/srv/docker'
92
+ port_whitelist:
93
+ 80: web-server
94
+ 443: web-server
95
+ 25: mail-server
96
+ 1337: bobblehead
97
+
98
+ The keys are:
99
+
100
+ * **`mount_root`**: the directory on disk where all mounts for all pods will
101
+ be stored. For security, the filesystem on which this location resides
102
+ should really be mounted `nosuid` and, if you can swing it, even `noexec`.
103
+ The directory specified by this option must already exist.
104
+
105
+ * **`port_whitelist`**: a map of port numbers and the pod which should be
106
+ allowed to map them. Any port which is not listed here cannot be explicitly
107
+ mapped by a pod, and only the pod named in the mapping can publish on the
108
+ specified port.
109
+
110
+ If you wish to modify the location of the `moby-derp` system-wide configuration
111
+ file, you can do so by setting the `MOBY_DERP_SYSTEM_CONFIG_FILE` environment
112
+ variable. Note, however, that it is a terrible idea to let ordinary users control
113
+ that environment variable, so if you want to set it, please write a small
114
+ wrapper script, like this:
115
+
116
+ #!/bin/sh
117
+
118
+ set -e
119
+
120
+ MOBY_DERP_SYSTEM_CONFIG_FILE=/opt/srv/etc/moby-derp/moby-derp.yaml
121
+
122
+ exec /usr/local/bin/moby-derp "$@"
123
+
124
+ You can also set other relevant environment variables, such as `DOCKER_HOST`,
125
+ in a like manner, if required.
126
+
127
+
128
+ ## Running `moby-derp` as a non-root user
129
+
130
+ If you are properly cautious, it is a fine idea to not allow `moby-derp` itself
131
+ root access to the system. This is possible, although it comes with a few
132
+ caveats:
133
+
134
+ * The `sudoers` config needs to be adjusted appropriately, to specify the
135
+ user that you want to run `moby-derp` as;
136
+
137
+ * The system-wide configuration file needs to be readable by whatever user
138
+ you run `moby-derp` as;
139
+
140
+ * When running `moby-derp`, the user to run it as needs to be specified on
141
+ the command line, like so:
142
+
143
+ sudo -u moby-derper moby-derp ./my-pod.yml
144
+
145
+ * The user that runs `moby-derp` must have access to the Docker control socket;
146
+ by default that means making that user a member of the `docker` group;
147
+
148
+ * The user that runs `moby-derp` must have write access to the `mount_root`
149
+ directory, so it can create pod mount roots.
150
+
151
+
152
+ # Security
153
+
154
+ This section discusses the security model and guarantees of `moby-derp`. It
155
+ isn't necessary to simply use `moby-derp` in most circumstances.
156
+
157
+ The fundamental principle of `moby-derp` is that users are given control over
158
+ a certain portion of the container and filesystem namespace, by virtue of their
159
+ ability to run `moby-derp` with a specific filename -- and nothing more. The
160
+ containers that a user can modify, and the portions of the filesystem that they
161
+ can write files to, is strictly limited by the tool.
162
+
163
+
164
+ ## Container namespace
165
+
166
+ All containers associated with a pod are named for the filename that is passed
167
+ to `moby-derp`. This means that, yes, different users need to use different
168
+ filenames. The benefit of this is that the `sudo` configuration becomes a lot
169
+ easier to audit -- the pod name is right there.
170
+
171
+ This means that no matter a user does, they cannot have any effect on any
172
+ container which is not named for the pod they're manipulating. There are also
173
+ safety valves around `moby-derp`-managed containers being labelled as such, so
174
+ that in the event that someone does inadvertently name a container in such a
175
+ way that it can be manipulated by another user via `moby-derp`, it should still
176
+ be safe from tampering.
177
+
178
+
179
+ ## Filesystem
180
+
181
+ All references to the host side of mounts within the pod are made relative to
182
+ the pod mount root, which is a subdirectory under the directory specified
183
+ under the `mount_root` system configuration named for the pod. As one would
184
+ expect, attempts to use absolute paths, or parent directory references (via
185
+ `..`) will not be looked upon kindly.
186
+
187
+ To prevent attacks around modifying file permissions in the container and then
188
+ using that on the host to escalate privileges, it is *STRONGLY* recommended
189
+ that the filesystem which holds the `mount_root` be mounted `nosuid`, and even
190
+ `noexec` if that isn't going to get in the way of your use-case. You
191
+ can also use the `userns-remap` feature if that is compatible with your use
192
+ of Moby.
193
+
194
+
195
+ ## Network ports
196
+
197
+ By default, `moby-derp` will not allow containers to publish to specific ports
198
+ on the host. The expectation is that network traffic will, for the most part,
199
+ rely on direct connection to containers, using either host-based proxies,
200
+ overlay networks, or new-fangled technologies like IPv6. This prevents
201
+ misbehaving pods from capturing ports intended for use by other pods. Pods can
202
+ still publish to ephemeral ports (using the `:containerPort` syntax, or
203
+ `publish_all: true`) if they wish.
204
+
205
+ If a pod *does* need to bind to a specific host port, then that pod/port pair
206
+ should be whitelisted in the system configuration file.
207
+
208
+
209
+ # Contributing
210
+
211
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
212
+
213
+
214
+ # Licence
215
+
216
+ Unless otherwise stated, everything in this repo is covered by the following
217
+ copyright notice:
218
+
219
+ Copyright (C) 2019 Matt Palmer <matt@hezmatt.org>
220
+
221
+ This program is free software: you can redistribute it and/or modify it
222
+ under the terms of the GNU General Public License version 3, as
223
+ published by the Free Software Foundation.
224
+
225
+ This program is distributed in the hope that it will be useful,
226
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
227
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
228
+ GNU General Public License for more details.
229
+
230
+ You should have received a copy of the GNU General Public License
231
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/bin/moby-derp ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "moby_derp/system_config"
4
+ require "moby_derp/pod_config"
5
+ require "moby_derp/pod"
6
+
7
+ require "docker-api"
8
+
9
+ if ARGV.length != 1
10
+ $stderr.puts "No config file specified"
11
+ exit 1
12
+ end
13
+
14
+ logger = Logger.new($stdout)
15
+ logger.formatter = ->(s, t, p, m) { "#{m}\n" }
16
+
17
+ case ENV["MOBY_DERP_LOG_LEVEL"]
18
+ when /\Adebug|info|warn|error\z/i
19
+ logger.level = Logger.const_get(ENV["MOBY_DERP_LOG_LEVEL"].upcase.to_sym)
20
+ when /\Atrace\z/i
21
+ logger.level = Logger::DEBUG
22
+ require "tracer"
23
+ Tracer.add_filter { |_e, _f, _l, _id, _b, klass, *_| klass.to_s =~ /MobyDerp/ }
24
+ Tracer.on
25
+ else
26
+ logger.level = Logger::INFO
27
+ end
28
+
29
+ begin
30
+ system_config_file = ENV["MOBY_DERP_SYSTEM_CONFIG_FILE"] || "/etc/moby-derp.conf"
31
+ system_config = MobyDerp::SystemConfig.new(system_config_file, Docker.info, logger)
32
+ rescue MobyDerp::ConfigurationError => ex
33
+ $stderr.puts "There was an error reading the system config file #{system_config_file}: #{ex.message}"
34
+ exit 1
35
+ rescue Excon::Error::Socket
36
+ $stderr.puts "Could not connect to the Moby server. Is it running?"
37
+ exit 1
38
+ end
39
+
40
+ begin
41
+ pod_config = MobyDerp::PodConfig.new(ARGV.first, system_config)
42
+ rescue MobyDerp::ConfigurationError => ex
43
+ $stderr.puts "There was an error reading the pod config file #{ARGV.first}: #{ex.message}"
44
+ exit 1
45
+ end
46
+
47
+ pod = MobyDerp::Pod.new(pod_config)
48
+
49
+ begin
50
+ pod.run
51
+ rescue MobyDerp::Error => ex
52
+ $stderr.puts "There was an error derping #{pod.name}: #{ex.message}"
53
+ exit 1
54
+ end
55
+
56
+ exit 0
data/example.yml ADDED
@@ -0,0 +1,297 @@
1
+ # This is an example pod specification file for moby-derp. It contains all
2
+ # the possible configuration options, with comments describing their use.
3
+ #
4
+ # The name of a pod spec file is important, because the file name is used as
5
+ # the name of the pod to manage, with any extension removed. Because of the
6
+ # limitations on container names imposed by Moby, this file name must, thus,
7
+ # start with a alphanumeric character, and consist entirely of alphanumeric
8
+ # characters, underscores, and hyphens.
9
+ #
10
+
11
+ # SECTION 1: CONTAINERS
12
+ #
13
+ # This is really what we're all here for, so let's get straight into it.
14
+ #
15
+ containers:
16
+ # The containers section contains a map of container names to the
17
+ # configuration parameters of that container. Each container's name
18
+ # must consist entirely of alphanumeric characters, underscores, and
19
+ # hyphens.
20
+ #
21
+ flingle:
22
+ # The most important parameter for a container -- and, in fact,
23
+ # the *only* mandatory one -- is the container **image**. The
24
+ # value given here must either be a symbolic image reference
25
+ # (name[:tag][@digest]) or a full image ID, including the digest
26
+ # identifier (`sha256:xyzzy123`).
27
+ #
28
+ image: 'moby-derp/flingle:latest'
29
+
30
+ # By default, if a symbolic image spec is provided in the `image` option,
31
+ # `moby-derp` will pull that image and recreate the container if the image
32
+ # ID has changed from what is currently running. If, for some reason, you
33
+ # do *not* want this behaviour for a specific container, set this option to
34
+ # `false`.
35
+ #
36
+ # Note that, in all cases, if the image ID underlying a symbolic
37
+ # image spec changes in the local daemon's image store, the container will
38
+ # be restarted.
39
+ #
40
+ update_image: false
41
+
42
+ # Many moby images require a command, or at least command-line
43
+ # options, in order to run correctly. To do this, set this
44
+ # key to whatever you need to pass.
45
+ #
46
+ # You can specify the command as a string, or as an array of strings if you
47
+ # want to avoid a layer or two of shell quoting.
48
+ #
49
+ command: '--foo=bar --wombat'
50
+
51
+ # The 12-factor app concept says that all configuration should be passed
52
+ # via the environment. If that is your bag, then this section will make
53
+ # you very happy.
54
+ #
55
+ environment:
56
+ # The environment is specified as a map of variable name to variable
57
+ # values. For passing big strings, you may want to get familiar with
58
+ # YAML's many string quoting and escaping modes.
59
+ #
60
+ APP_ENV: production
61
+ # Embedding your private key like this isn't necessarily a particularly
62
+ # good idea, but as an example of where you might need multiline strings,
63
+ # it works very well.
64
+ #
65
+ PRIVATE_KEY: |
66
+ -----BEGIN PRIVATE KEY-----
67
+ AAAAsd...
68
+ -----END PRIVATE KEY-----
69
+
70
+ # Persisting data past the lifetime of a particular container is the job
71
+ # of mounts. Here you can specify filesystem locations on the host
72
+ # (relative to the pod's mount root) and where they should be
73
+ # mounted in the container.
74
+ #
75
+ # The list of mounts is an array of hashes, each of which specifies
76
+ # one mount.
77
+ #
78
+ mounts:
79
+ # The `source` parameter is a *relative* path (relative to the pod's
80
+ # mount root) on the host's filesystem. Typically it will be a
81
+ # single descriptive word, but you can delve into subdirectories if
82
+ # you so choose.
83
+ #
84
+ - source: app
85
+ # The `target` is an absolute path within the container, where the
86
+ # associated host directory should be mounted.
87
+ #
88
+ target: /app
89
+
90
+ - source: static-assets
91
+ target: /app/public
92
+
93
+ # By default, mounts are read-write -- that is, if a process in the
94
+ # container has the relevant permissions, files in the mount can
95
+ # be modified, created, and deleted. If you absolutely, positively
96
+ # want to make sure that can't happen, set `readonly: true`, and we'll
97
+ # tell Moby to make it so.
98
+ #
99
+ readonly: true
100
+
101
+ # Sometimes you need to add a label to a specific container.
102
+ # This is how you do that.
103
+ #
104
+ labels:
105
+ # It's very simple: `labelname: labelvalue`.
106
+ #
107
+ somelabel: 'Some mighty value'
108
+
109
+ # Mark the container's *root* filesystem as read-only. Not *typically*
110
+ # a good idea.
111
+ #
112
+ readonly: true
113
+
114
+ # Specify the signal that should be sent to the container in order to
115
+ # cause it to stop gracefully. Can be specified as a number, or a
116
+ # string with or without the leading `SIG`.
117
+ #
118
+ stop_signal: SIGQUIT
119
+
120
+ # How long, in seconds, to wait for the container to gracefully stop
121
+ # before whacking it with a `SIGKILL`.
122
+ #
123
+ stop_timeout: 42
124
+
125
+ # Adjust the container's restart policy.
126
+ #
127
+ restart: on-failure:42
128
+
129
+ # Here you can place limits on your containers, to prevent them
130
+ # accidentally blowing up the world. Note that, in keeping with
131
+ # moby-derp's philosophy of limited privilege, you can only reduce
132
+ # the limits on a container, not raise them above what you would
133
+ # get by default (a bit like ulimits for non-root users).
134
+ #
135
+ limits:
136
+ # How many CPUs' worth of CPU time to allow this container to
137
+ # use.
138
+ #
139
+ cpus: 1.75
140
+
141
+ # A relative "share" of CPU time, calculated as a ratio of the
142
+ # total CPU shares granted to all cgroups. The default of 1024
143
+ # is the maximum you can set.
144
+ #
145
+ cpu-shares: 72
146
+
147
+ # Set memory and swap limits.
148
+ #
149
+ # These two settings have complicated inter-relationships. You
150
+ # definitely want to refer to
151
+ # https://docs.docker.com/config/containers/resource_constraints/#--memory-swap-details
152
+ # before using these. You may also want to look at
153
+ # https://docs.docker.com/install/linux/linux-postinstall/#your-kernel-does-not-support-cgroup-swap-limit-capabilities
154
+ #
155
+ memory: 1G
156
+ memory-swap: 42M
157
+
158
+ # The "soft limit" for memory usage -- if your container's memory
159
+ # usage is above this limit, and the overall system is running low on
160
+ # memory, this container will be pressured to reduce its memory
161
+ # usage.
162
+ #
163
+ memory-reservation: 100M
164
+
165
+ # Increase a container's propensity to have its processes whacked by
166
+ # the OOM killer, if such a thing becomes necessary. The value must
167
+ # be in the range 0 to 1000.
168
+ #
169
+ oom-score-adj: 42
170
+
171
+ # Limit the number of processes this container is allowed to create.
172
+ #
173
+ pids: 65535
174
+
175
+ # The amount of shared memory the container is allowed to use.
176
+ #
177
+ shm-size: 42G
178
+
179
+ # Set ulimits.
180
+ #
181
+ # All of the below settings are related to ulimits, which are the
182
+ # old-school, but still surprisingly effective, version of resource
183
+ # limits, from before we had cgroups. All of the values take the
184
+ # form `<soft limit>[:<hard limit>]`, and if `<hard limit>` is not
185
+ # provided, it is set equal to the specified `<soft limit>`. The
186
+ # soft limit must be less than or equal to the hard limit.
187
+ #
188
+ # See `getrlimit`(2) for what each limit means, as well as
189
+ # https://docs.docker.com/engine/reference/commandline/#set-ulimits-in-container---ulimit
190
+ # for Moby-specific restrictions and caveats. As an additional
191
+ # undocumented caveat, if you want to set no limit for a resource,
192
+ # specify the string "unlimited".
193
+ #
194
+ # No value, for hard *or* soft limits, can be set higher than the
195
+ # hard limits for the `dockerd` process, although you won't know
196
+ # that you've broken the limits until `moby-derp` tries to start the
197
+ # container and the wheels fall off.
198
+ #
199
+ ulimit-core: 1:2
200
+ ulimit-cpu: 3:4
201
+ ulimit-data: 5:6
202
+ ulimit-fsize: 7:8
203
+ ulimit-memlock: 9:10
204
+ ulimit-msgqueue: 11:12
205
+ ulimit-nofile: 13:14
206
+ ulimit-rttime: 15:16
207
+ ulimit-stack: 17:18
208
+
209
+ # SECTION 2: POD-LEVEL CONFIGURATION
210
+ #
211
+ # The remainder of the configuration items in this file correspond to settings
212
+ # which apply to the pod -- either defaults for all containers in the pod, or
213
+ # else settings which only apply to the "root" container in the pod (the container
214
+ # which holds the networking, IPC, and PID namespaces).
215
+
216
+ # Set a custom hostname for the container.
217
+ #
218
+ # The default is `<podname>-<host hostname>`, which works surprisingly well
219
+ # in a majority of cases. It's certainly less wild than the Moby default
220
+ # of the short container ID.
221
+ #
222
+ hostname: bobtown
223
+
224
+ # Common environment which will apply to all containers.
225
+ #
226
+ # These environment variables will be set for all containers, including the
227
+ # pod "root" container. Individual containers can override the value for an
228
+ # environment variable, by setting their own value, but they *cannot* cause
229
+ # an environment variable to be unset.
230
+ #
231
+ common_environment:
232
+ # As is the case for container-level environment variables, each key/value
233
+ # pair in the map is one environment variable name to value mapping.
234
+ #
235
+ AWESOME: yes
236
+
237
+ # Common labels which will apply to all containers.
238
+ #
239
+ # These labels will be applied to every container in the pod, including the
240
+ # pod "root" container. Like common environment variables, an individual
241
+ # container can override the value of a common label, but cannot cause the
242
+ # label to be unset entirely.
243
+ #
244
+ common_labels:
245
+ mah_pod: is on fire
246
+
247
+ # Labels that *only* apply to the "root" container of the pod.
248
+ #
249
+ # Some labels, often those associated with a container's network config
250
+ # (like service discovery), should only be applied to the pod's "root"
251
+ # container. Those labels should be defined here.
252
+ #
253
+ root_labels:
254
+ service: over here
255
+
256
+ # Common mount definitions that should apply to all containers.
257
+ #
258
+ # If you have a mount that every container in the pod should have access
259
+ # to, you really don't want to have to specify it every time. Instead,
260
+ # you can put them here. The format is the same as the per-container
261
+ # `mounts` option.
262
+ #
263
+ common_mounts:
264
+ - source: app
265
+ target: /app
266
+ - source: static-assets
267
+ target: /app/public
268
+ readonly: true
269
+
270
+ # A list of ports in the pod to expose.
271
+ #
272
+ # Marking a port as "exposed" interacts with Moby's "publish all" option,
273
+ # as well as with service advertisement systems.
274
+ #
275
+ expose:
276
+ - 80
277
+ - 443
278
+ - 1337
279
+
280
+ # A list of port publishing specifications.
281
+ #
282
+ # If you wish to use Moby's port forward/proxying mechanisms, you do that by
283
+ # "publishing" the port(s) you want to use. Note that, to prevent errant pods
284
+ # from capturing ports they're not supposed to, by default pods can only publish
285
+ # to ephemeral ports (no `hostPort` specification). If a particular pod
286
+ # *should* be able to publish a specific host port, it needs to be whitelisted
287
+ # in the system-wide configuration (see the `port_whitelist` documentation
288
+ # in the `moby-derp` README).
289
+ #
290
+ publish:
291
+ - :80
292
+ - :1234-1237
293
+
294
+ # If you have a burning desire to have all exposed ports automatically published
295
+ # to (not-so-)randomly chosen ephemeral ports, you can set this option to `true`.
296
+ #
297
+ publish_all: true
@@ -0,0 +1,23 @@
1
+ module Docker
2
+ class Image
3
+ # https://github.com/docker/distribution/blob/master/reference/reference.go
4
+ # as at 2019-04-23
5
+ DIGEST_HEX = /[0-9a-fA-F]{32,}/
6
+ DIGEST_ALGORITHM_COMPONENT = /[A-Za-z][A-Za-z0-9]*/
7
+ DIGEST_ALGORITHM_SEPARATOR = /[+._-]/
8
+ DIGEST_ALGORITHM = /#{DIGEST_ALGORITHM_COMPONENT}(#{DIGEST_ALGORITHM_SEPARATOR}#{DIGEST_ALGORITHM_COMPONENT})*/
9
+ DIGEST = /#{DIGEST_ALGORITHM}:#{DIGEST_HEX}/
10
+
11
+ TAG = /[\w][\w.-]{0,127}/
12
+
13
+ SEPARATOR = /[_.]|__|[-]*/
14
+ ALPHANUMERIC = /[a-z0-9]+/
15
+ PATH_COMPONENT = /#{ALPHANUMERIC}(#{SEPARATOR}#{ALPHANUMERIC})*/
16
+ PORT_NUMBER = /\d+/
17
+ DOMAIN_COMPONENT = /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
18
+ DOMAIN = /#{DOMAIN_COMPONENT}(\.#{DOMAIN_COMPONENT})*(:#{PORT_NUMBER})?/
19
+ NAME = /(#{DOMAIN}\/)?#{PATH_COMPONENT}(\/#{PATH_COMPONENT})*/
20
+ IMAGE_REFERENCE = /\A#{NAME}(:#{TAG})?(@#{DIGEST})?\z/
21
+ end
22
+ end
23
+
@@ -0,0 +1,33 @@
1
+ require_relative "./error"
2
+ require_relative "./logging_helpers"
3
+
4
+ require "safe_yaml"
5
+
6
+ module MobyDerp
7
+ class ConfigFile
8
+ include LoggingHelpers
9
+
10
+ attr_reader :logger
11
+
12
+ def initialize(filename)
13
+ begin
14
+ @logger.debug(logloc) { "Reading configuration file #{filename}" }
15
+ @config = SafeYAML.load(File.read(filename))
16
+ rescue Errno::ENOENT
17
+ raise ConfigurationError,
18
+ "file does not exist"
19
+ rescue Errno::EPERM
20
+ raise ConfigurationError,
21
+ "cannot read file"
22
+ rescue Psych::SyntaxError => ex
23
+ raise ConfigurationError,
24
+ "invalid YAML syntax: #{ex.message}"
25
+ end
26
+
27
+ unless @config.is_a?(Hash)
28
+ raise ConfigurationError,
29
+ "invalid file contents -- must be a map"
30
+ end
31
+ end
32
+ end
33
+ end