Skip to content

Commit 742c1e7

Browse files
committed
Add some sudoers exception so that the rails user can run the isolated ruby rake task
1 parent 7a57aba commit 742c1e7

File tree

4 files changed

+244
-135
lines changed

4 files changed

+244
-135
lines changed

Diff for: Dockerfile

+61-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ RUN apt-get update -qq && \
1818
ENV RAILS_ENV="production" \
1919
BUNDLE_WITHOUT="development:test" \
2020
BUNDLE_DEPLOYMENT="1" \
21-
BUNDLE_PATH="/usr/local/bundle"
21+
BUNDLE_PATH="/usr/local/bundle" \
22+
RAILS_BUILD="1"
2223

2324
FROM base AS nodejs
2425

@@ -69,14 +70,70 @@ FROM base
6970
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
7071
COPY --from=build /rails /rails
7172

72-
# Run and own only the runtime files as a non-root user for security
73+
# Create the rails user first
7374
RUN groupadd --system --gid 1000 rails && \
7475
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
76+
echo "rails ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /var/lib/rails-new-io/workspaces, /usr/bin/chown rails\:rails /var/lib/rails-new-io/workspaces" > /etc/sudoers.d/rails
77+
78+
# Set up isolated Ruby environment
79+
RUN mkdir -p /var/lib/rails-new-io/rails-env/ruby && \
80+
cp -r /usr/local/* /var/lib/rails-new-io/rails-env/ruby/ && \
81+
mkdir -p /var/lib/rails-new-io/rails-env/gems && \
82+
mkdir -p /var/lib/rails-new-io/rails-env/bundle && \
7583
mkdir -p /var/lib/rails-new-io/workspaces && \
84+
# Disable documentation installation
85+
echo "gem: --no-document" > /var/lib/rails-new-io/rails-env/bundle/gemrc && \
86+
# Install bundler with exact same env as setup.rake
87+
PATH=/var/lib/rails-new-io/rails-env/ruby/bin:/usr/local/bin:/usr/bin:/bin \
88+
GEM_HOME=/var/lib/rails-new-io/rails-env/gems \
89+
GEM_PATH=/var/lib/rails-new-io/rails-env/gems \
90+
BUNDLE_USER_HOME=/var/lib/rails-new-io/rails-env/bundle \
91+
BUNDLE_GEMFILE="" \
92+
BUNDLE_BIN="" \
93+
BUNDLE_PATH="" \
94+
BUNDLE_APP_CONFIG="" \
95+
RUBYOPT="" \
96+
RUBYLIB="" \
97+
RUBY_ROOT=/var/lib/rails-new-io/rails-env/ruby \
98+
RUBY_VERSION=${RUBY_VERSION} \
99+
ASDF_DIR="" \
100+
ASDF_DATA_DIR="" \
101+
ASDF_RUBY_VERSION="" \
102+
RBENV_VERSION="" \
103+
RBENV_ROOT="" \
104+
rvm_bin_path="" \
105+
rvm_path="" \
106+
RUBY_AUTO_VERSION="" \
107+
/var/lib/rails-new-io/rails-env/ruby/bin/gem install bundler -v 2.6.3 && \
108+
# Install Rails with exact same env as setup.rake
109+
PATH=/var/lib/rails-new-io/rails-env/ruby/bin:/usr/local/bin:/usr/bin:/bin \
110+
GEM_HOME=/var/lib/rails-new-io/rails-env/gems \
111+
GEM_PATH=/var/lib/rails-new-io/rails-env/gems \
112+
BUNDLE_USER_HOME=/var/lib/rails-new-io/rails-env/bundle \
113+
BUNDLE_GEMFILE="" \
114+
BUNDLE_BIN="" \
115+
BUNDLE_PATH="" \
116+
BUNDLE_APP_CONFIG="" \
117+
RUBYOPT="" \
118+
RUBYLIB="" \
119+
RUBY_ROOT=/var/lib/rails-new-io/rails-env/ruby \
120+
RUBY_VERSION=${RUBY_VERSION} \
121+
ASDF_DIR="" \
122+
ASDF_DATA_DIR="" \
123+
ASDF_RUBY_VERSION="" \
124+
RBENV_VERSION="" \
125+
RBENV_ROOT="" \
126+
rvm_bin_path="" \
127+
rvm_path="" \
128+
RUBY_AUTO_VERSION="" \
129+
/var/lib/rails-new-io/rails-env/ruby/bin/gem install rails -v 8.0.1
130+
131+
# Now we can chown everything since the user exists
132+
RUN chown -R rails:rails /var/lib/rails-new-io && \
133+
chmod -R 755 /var/lib/rails-new-io && \
76134
chown -R rails:rails /rails && \
77135
chmod -R 755 /rails && \
78-
chown -R rails:rails db log storage tmp /usr/local/bundle /var/lib/rails-new-io && \
79-
echo "rails ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /var/lib/rails-new-io/workspaces, /usr/bin/chown rails\:rails /var/lib/rails-new-io/workspaces" > /etc/sudoers.d/rails
136+
chown -R rails:rails db log storage tmp /usr/local/bundle
80137

81138
USER rails
82139

Diff for: app/services/command_execution_service.rb

+122-69
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,10 @@ def setup_work_directory
178178

179179
def ruby_platform
180180
@ruby_platform ||= begin
181-
platform_cmd = "#{RAILS_GEN_ROOT}/ruby/bin/ruby -e 'puts RUBY_DESCRIPTION.split.last'"
181+
platform_cmd = "#{RAILS_GEN_ROOT}/ruby/bin/ruby -e 'puts RUBY_PLATFORM'"
182182
platform = `#{platform_cmd}`.strip
183183

184-
if platform.empty?
184+
if platform.blank?
185185
# Fallback in case the command fails
186186
case RbConfig::CONFIG["host_os"]
187187
when /darwin/
@@ -195,10 +195,14 @@ def ruby_platform
195195
end
196196
end
197197

198+
def platform_path
199+
@platform_path ||= "#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0/#{ruby_platform}"
200+
end
201+
198202
def ruby_lib_paths
199203
[
200204
"#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0",
201-
"#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0/#{ruby_platform}"
205+
platform_path
202206
].join(":")
203207
end
204208

@@ -221,25 +225,19 @@ def run_isolated_process
221225
# Ruby
222226
"GEM_HOME" => "#{RAILS_GEN_ROOT}/gems",
223227
"GEM_PATH" => "#{RAILS_GEN_ROOT}/gems",
224-
"RUBYOPT" => nil,
225-
"RUBYLIB" => nil,
226-
"RUBY_ROOT" => ruby_dir,
228+
"RUBYOPT" => "--disable-gems",
229+
"RUBYLIB" => ruby_lib_paths,
230+
"RUBY_ROOT" => "#{RAILS_GEN_ROOT}/ruby",
227231
"RUBY_ENGINE" => "ruby",
228232
"RUBY_VERSION" => RUBY_VERSION,
229233
"RUBY_PATCHLEVEL" => nil,
230-
# asdf (extra thorough)
234+
# asdf
231235
"ASDF_DIR" => nil,
232236
"ASDF_DATA_DIR" => nil,
233237
"ASDF_CONFIG_FILE" => nil,
234238
"ASDF_DEFAULT_TOOL_VERSIONS_FILENAME" => nil,
235239
"ASDF_RUBY_VERSION" => nil,
236240
"ASDF_GEM_HOME" => nil,
237-
"ASDF_RUBY_PATH" => nil,
238-
"ASDF_RUBY_HOME" => nil,
239-
"ASDF_RUBY_ROOT" => nil,
240-
"ASDF_RUBY_GEMS_PATH" => nil,
241-
"ASDF_RUBY_GEMS_HOME" => nil,
242-
"ASDF_RUBY_GEMS_ROOT" => nil,
243241
# RVM
244242
"rvm_bin_path" => nil,
245243
"rvm_path" => nil,
@@ -251,13 +249,10 @@ def run_isolated_process
251249
"RBENV_ROOT" => nil,
252250
# chruby
253251
"RUBY_AUTO_VERSION" => nil,
254-
# Minimal PATH with only what we need, ensuring our Ruby is first
252+
# Minimal PATH with only what we need
255253
"PATH" => "#{ruby_dir}/bin:#{RAILS_GEN_ROOT}/gems/bin:/usr/local/bin:/usr/bin:/bin",
256254
# Extra insurance
257255
"SHELL" => "/bin/bash",
258-
# Force Ruby to ignore other gem paths
259-
"RUBYOPT" => "--disable-gems", # Start clean
260-
"RUBYLIB" => ruby_lib_paths, # Only our paths
261256
# Prevent Ruby from looking in user directories
262257
"HOME" => "/var/lib/rails-new-io/home",
263258
# Prevent loading of any user config
@@ -266,76 +261,77 @@ def run_isolated_process
266261
"XDG_DATA_HOME" => "/var/lib/rails-new-io/data"
267262
}
268263

264+
@logger.debug("Environment before command execution", {
265+
gem_home: ENV["GEM_HOME"],
266+
gem_path: ENV["GEM_PATH"],
267+
bundle_path: ENV["BUNDLE_PATH"],
268+
bundle_gemfile: ENV["BUNDLE_GEMFILE"],
269+
rubylib: ENV["RUBYLIB"],
270+
load_path: $LOAD_PATH
271+
})
272+
269273
# Create isolation directories
270274
FileUtils.mkdir_p("/var/lib/rails-new-io/home")
271275
FileUtils.mkdir_p("/var/lib/rails-new-io/config")
272276
FileUtils.mkdir_p("/var/lib/rails-new-io/cache")
273277
FileUtils.mkdir_p("/var/lib/rails-new-io/data")
274278

275279
env = if @command.start_with?("rails new")
276-
base_env
277-
elsif @command.start_with?("bundle")
278-
# For bundle commands, be extra explicit about the environment
279280
base_env.merge(
280-
"BUNDLE_GEMFILE" => File.join(@work_dir, "Gemfile"),
281-
"BUNDLE_APP_CONFIG" => File.join(@work_dir, ".bundle"),
282-
"BUNDLE_USER_HOME" => "#{RAILS_GEN_ROOT}/bundle",
283-
"BUNDLE_DISABLE_SHARED_GEMS" => "true",
284281
"GEM_HOME" => "#{RAILS_GEN_ROOT}/gems",
285282
"GEM_PATH" => "#{RAILS_GEN_ROOT}/gems",
286-
"RUBYOPT" => "--disable-gems", # Start clean
283+
"BUNDLE_USER_HOME" => "#{RAILS_GEN_ROOT}/bundle",
284+
"BUNDLE_GEMFILE" => "",
285+
"BUNDLE_BIN" => "",
286+
"BUNDLE_PATH" => "",
287+
"BUNDLE_APP_CONFIG" => "",
288+
"RUBYOPT" => "",
287289
"RUBYLIB" => ruby_lib_paths,
288-
"PATH" => "#{ruby_dir}/bin:#{RAILS_GEN_ROOT}/gems/bin:/usr/local/bin:/usr/bin:/bin",
289-
# Extra insurance against asdf
290-
"ASDF_RUBY_VERSION" => nil,
291-
"ASDF_GEM_HOME" => nil,
292-
"ASDF_RUBY_PATH" => nil,
293-
"ASDF_RUBY_HOME" => nil,
294-
"ASDF_RUBY_ROOT" => nil,
295-
"ASDF_RUBY_GEMS_PATH" => nil,
296-
"ASDF_RUBY_GEMS_HOME" => nil,
297-
"ASDF_RUBY_GEMS_ROOT" => nil
290+
"RUBY_ROOT" => "#{RAILS_GEN_ROOT}/ruby",
291+
"RUBY_VERSION" => RUBY_VERSION,
292+
"PATH" => "#{RAILS_GEN_ROOT}/ruby/bin:#{RAILS_GEN_ROOT}/gems/bin:/usr/local/bin:/usr/bin:/bin"
298293
)
299-
elsif @command.start_with?("rails app:template")
294+
elsif @command.start_with?("bundle")
295+
# For bundle commands, be extra explicit about the environment
300296
base_env.merge(
301-
"BUNDLE_GEMFILE" => File.join(@work_dir, "Gemfile"),
302-
"BUNDLE_APP_CONFIG" => File.join(@work_dir, ".bundle"),
303-
"PATH" => "#{ruby_dir}/bin:#{RAILS_GEN_ROOT}/gems/bin:#{File.join(@work_dir, 'bin')}:/usr/local/bin:/usr/bin:/bin"
297+
"GEM_HOME" => "#{RAILS_GEN_ROOT}/gems",
298+
"GEM_PATH" => "#{RAILS_GEN_ROOT}/gems",
299+
"BUNDLE_USER_HOME" => "#{RAILS_GEN_ROOT}/bundle",
300+
"BUNDLE_GEMFILE" => "",
301+
"BUNDLE_BIN" => "",
302+
"BUNDLE_PATH" => "",
303+
"BUNDLE_APP_CONFIG" => "",
304+
"RUBYOPT" => "",
305+
"RUBYLIB" => ruby_lib_paths,
306+
"RUBY_ROOT" => "#{RAILS_GEN_ROOT}/ruby",
307+
"RUBY_VERSION" => RUBY_VERSION,
308+
"PATH" => "#{RAILS_GEN_ROOT}/ruby/bin:#{RAILS_GEN_ROOT}/gems/bin:/usr/local/bin:/usr/bin:/bin"
304309
)
305310
else
306311
base_env.merge(
307-
"BUNDLE_GEMFILE" => File.join(@work_dir, "Gemfile"),
308-
"PATH" => "#{ruby_dir}/bin:#{RAILS_GEN_ROOT}/gems/bin:#{File.join(@work_dir, 'bin')}:/usr/local/bin:/usr/bin:/bin"
312+
"GEM_HOME" => "#{RAILS_GEN_ROOT}/gems",
313+
"GEM_PATH" => "#{RAILS_GEN_ROOT}/gems",
314+
"BUNDLE_USER_HOME" => "#{RAILS_GEN_ROOT}/bundle",
315+
"BUNDLE_GEMFILE" => "",
316+
"BUNDLE_BIN" => "",
317+
"BUNDLE_PATH" => "",
318+
"BUNDLE_APP_CONFIG" => "",
319+
"RUBYOPT" => "",
320+
"RUBYLIB" => ruby_lib_paths,
321+
"RUBY_ROOT" => "#{RAILS_GEN_ROOT}/ruby",
322+
"RUBY_VERSION" => RUBY_VERSION,
323+
"PATH" => "#{RAILS_GEN_ROOT}/ruby/bin:#{RAILS_GEN_ROOT}/gems/bin:/usr/local/bin:/usr/bin:/bin"
309324
)
310325
end
311326

312-
bundle_command = if @command.start_with?("rails new")
313-
@command
314-
elsif @command.start_with?("bundle")
315-
# For bundle commands, bypass the bundle executable entirely and run the code directly
316-
command_parts = @command.split
317-
bundle_args = command_parts[1..-1].join(" ")
318-
[
319-
"#{RAILS_GEN_ROOT}/ruby/bin/ruby",
320-
"-I#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0",
321-
"-I#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0/#{ruby_platform}",
322-
"-e",
323-
"'require \"bundler\"; Bundler::CLI.start([#{bundle_args.split.map { |arg| %Q(\"#{arg}\") }.join(", ")}])'"
324-
].join(" ")
325-
else
326-
# Instead of using ./bin/rails, explicitly use our Ruby and load bundler from Ruby's installation
327-
command_parts = @command.split
328-
command_parts[0] = [
329-
"#{RAILS_GEN_ROOT}/ruby/bin/ruby",
330-
"-I#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0",
331-
"-I#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0/#{ruby_platform}",
332-
"-I#{RAILS_GEN_ROOT}/gems/gems",
333-
"-r bundler/setup",
334-
"-e",
335-
"'load \"#{RAILS_GEN_ROOT}/gems/bin/rails\"'"
336-
].join(" ")
337-
command_parts.join(" ")
338-
end
327+
@logger.debug("Environment after command execution setup", {
328+
gem_home: env["GEM_HOME"],
329+
gem_path: env["GEM_PATH"],
330+
bundle_path: env["BUNDLE_PATH"],
331+
bundle_gemfile: env["BUNDLE_GEMFILE"],
332+
rubylib: env["RUBYLIB"],
333+
command: bundle_command
334+
})
339335

340336
# For non-rails-new commands, we need to run bundle install first
341337
if !@command.start_with?("rails new") && !@command.start_with?("bundle")
@@ -379,6 +375,7 @@ def run_isolated_process
379375
stderr_thread = Thread.new do
380376
stderr.each_line do |line|
381377
error_buffer << line.strip
378+
@logger.debug("Command stderr: #{line.strip}")
382379
end
383380
end
384381

@@ -393,7 +390,16 @@ def run_isolated_process
393390
@logger.error("Command failed", {
394391
status: exit_status,
395392
output: output,
396-
error_buffer: error_buffer.join("<br>")
393+
error_buffer: error_buffer.join("<br>"),
394+
command: bundle_command,
395+
directory: @work_dir,
396+
load_paths: [
397+
"#{RAILS_GEN_ROOT}/ruby/lib/ruby/3.4.0",
398+
platform_path,
399+
"#{RAILS_GEN_ROOT}/gems/gems",
400+
"#{RAILS_GEN_ROOT}/gems/gems/rails-#{RAILS_VERSION}/lib",
401+
"#{RAILS_GEN_ROOT}/gems/gems/railties-#{RAILS_VERSION}/lib"
402+
]
397403
})
398404
raise "Command failed with status: #{exit_status}"
399405
end
@@ -475,4 +481,51 @@ def jemalloc_lib_path
475481
"/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
476482
end
477483
end
484+
485+
def bundle_command
486+
@logger.debug("Starting bundle_command construction")
487+
@logger.debug("Command is: #{@command}")
488+
@logger.debug("Platform path is: #{platform_path}")
489+
490+
# Debug: Check what's in the gems directory
491+
@logger.debug("Checking Rails installation", {
492+
rails_cli_path: "#{RAILS_GEN_ROOT}/gems/gems/railties-#{RAILS_VERSION}/lib/rails/cli.rb",
493+
rails_cli_exists: File.exist?("#{RAILS_GEN_ROOT}/gems/gems/railties-#{RAILS_VERSION}/lib/rails/cli.rb"),
494+
rails_lib_path: "#{RAILS_GEN_ROOT}/gems/gems/railties-#{RAILS_VERSION}/lib",
495+
rails_lib_exists: File.exist?("#{RAILS_GEN_ROOT}/gems/gems/railties-#{RAILS_VERSION}/lib"),
496+
rails_gem_path: "#{RAILS_GEN_ROOT}/gems/gems/rails-#{RAILS_VERSION}",
497+
rails_gem_exists: File.exist?("#{RAILS_GEN_ROOT}/gems/gems/rails-#{RAILS_VERSION}"),
498+
gem_home_contents: Dir.glob("#{ENV['GEM_HOME']}/*").join(", "),
499+
gem_home_gems: Dir.glob("#{ENV['GEM_HOME']}/gems/*").join(", "),
500+
rails_gem_contents: Dir.glob("#{RAILS_GEN_ROOT}/gems/gems/rails-#{RAILS_VERSION}/*").join(", "),
501+
railties_gem_contents: Dir.glob("#{RAILS_GEN_ROOT}/gems/gems/railties-#{RAILS_VERSION}/*").join(", ")
502+
})
503+
504+
# Debug: Check load paths
505+
@logger.debug("Ruby load paths", {
506+
load_path: $LOAD_PATH,
507+
gem_path: Gem.path,
508+
gem_paths_exist: Gem.path.map { |p| [ p, Dir.exist?(p) ] }.to_h
509+
})
510+
511+
command = if @command.start_with?("rails new")
512+
# For rails new, use the rails executable
513+
command_parts = @command.split
514+
command_parts[0] = "#{RAILS_GEN_ROOT}/gems/bin/rails"
515+
command_parts.join(" ").tap { |cmd| @logger.debug("Constructed rails new command: #{cmd}") }
516+
elsif @command.start_with?("bundle")
517+
# For bundle commands, use the bundle executable
518+
command_parts = @command.split
519+
command_parts[0] = "#{RAILS_GEN_ROOT}/gems/bin/bundle"
520+
command_parts.join(" ").tap { |cmd| @logger.debug("Constructed bundle command: #{cmd}") }
521+
else
522+
# For other rails commands, use the rails executable
523+
command_parts = @command.split
524+
command_parts[0] = "#{RAILS_GEN_ROOT}/gems/bin/rails"
525+
command_parts.join(" ").tap { |cmd| @logger.debug("Constructed other command: #{cmd}") }
526+
end
527+
528+
@logger.debug("Final command: #{command}")
529+
command
530+
end
478531
end

Diff for: app/views/ingredients/show.html.erb

+9-5
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
</div>
4343
<div class="pt-8">
4444
<h3 class="text-lg font-medium leading-6 text-gray-900">Screenshots</h3>
45-
<div class="mt-4 grid grid-cols-2 gap-4">
46-
<div>
45+
<div class="mt-4 flex space-x-4">
46+
<div class="flex-1">
4747
<h4 class="text-sm font-medium text-gray-700 mb-2">Before Screenshot</h4>
4848
<% if @ingredient.before_screenshot.attached? %>
49-
<%= image_tag @ingredient.before_screenshot, class: "w-full h-48 object-cover rounded-lg" %>
49+
<div class="bg-gray-50 rounded-lg p-1">
50+
<%= image_tag @ingredient.before_screenshot, class: "w-full rounded-lg" %>
51+
</div>
5052
<% else %>
5153
<div class="w-full h-48 bg-gray-100 flex items-center justify-center rounded-lg">
5254
<span class="text-gray-400">No before screenshot</span>
@@ -61,10 +63,12 @@
6163
<% end %>
6264
</div>
6365

64-
<div>
66+
<div class="flex-1">
6567
<h4 class="text-sm font-medium text-gray-700 mb-2">After Screenshot</h4>
6668
<% if @ingredient.after_screenshot.attached? %>
67-
<%= image_tag @ingredient.after_screenshot, class: "w-full h-48 object-cover rounded-lg" %>
69+
<div class="bg-gray-50 rounded-lg p-1">
70+
<%= image_tag @ingredient.after_screenshot, class: "w-full rounded-lg" %>
71+
</div>
6872
<% else %>
6973
<div class="w-full h-48 bg-gray-100 flex items-center justify-center rounded-lg">
7074
<span class="text-gray-400">No after screenshot</span>

0 commit comments

Comments
 (0)