Compare commits

..

No commits in common. "20c0e0f5de181c79b741353f8445c365c7cdd27c" and "ae328f465c57d73d4de14e06c7ecef2fc57513aa" have entirely different histories.

6 changed files with 59 additions and 148 deletions

View File

@ -1,9 +1,9 @@
# This configuration file was generated by `ameba --gen-config`
# on 2024-07-01 13:11:14 UTC using Ameba version 1.6.1.
# on 2024-06-30 18:10:12 UTC using Ameba version 1.6.1.
# The point is for the user to remove these configuration records
# one by one as the reported problems are removed from the code base.
# Problems found: 11
# Problems found: 10
# Run `ameba --only Documentation/DocumentationAdmonition` for details
Documentation/DocumentationAdmonition:
Description: Reports documentation admonitions
@ -12,7 +12,6 @@ Documentation/DocumentationAdmonition:
- src/faaso.cr
- src/daemon.cr
- src/funko.cr
- src/daemon-secrets.cr
- spec/faaso_spec.cr
Admonitions:
- TODO

View File

@ -6,10 +6,10 @@ However, code running in docker containers like a funko does often needs
access to secrets, such as passwords to databases. Let's use that as the
example secret for the rest of the document.
Also, not all funkos should have access to all the secrets, and the
Also, not all funkos should have access to all the secrets, and the
"need to know" should be declarative in the funko's metadata.
## Problem 1: accessing secrets in the proxy container from the funkos
## Problem 1: accessing secrets in the proxy container from the funkos
Let's further assume that faaso-proxy has access *somehow* to all the secrets,
identified by a name. So there is a "dbpass" secret that has "verysecret" as
@ -18,32 +18,4 @@ it's very secret content.
Also let's assume the proxy has access to a folder in the server filesystem,
`/secrets` via something like a bind mount.
If the proxy has access to the funko metadata, it can access a declaration of
what secrets the funko needs.
Alternatively ... convention!
Secrets for the funko foo are called foo-{name}, so our example is called foo-dbpass.
So, the proxy can periodically examine its secret store and populate a folder
`/secrets/foo` with a `dbpass` file containing "verysecret".
If on starting a funko we always do a bind mount of `/secrets/foo` to `/secrets`
then it will always have its secrets in place.
## Problem 2: how can the proxy know the secrets without keeping them in the image?
They can't be shipped via the image, so they need to be injected via the admin API.
Let's give it a /secret endpoint and have all the usual REST stuff there.
## The Good
* It should work
* No secrets are unencrypted at rest in images
## The Bad
* Secrets are unencrypted at rest in the server filesystem
* Secrets are only sort-of-persistent? If the proxy is restarted, it will need
the secrets reinjected, or we need a persistent secret store in the server filesystem.
## Problem 2: how can the proxy know the secrets without keeping them in the image?

View File

@ -1,59 +0,0 @@
require "kemal"
module Proxy
@@current_config = File.read("tinyproxy.conf")
# Get current proxy config
get "/proxy/" do
@@current_config
end
# Bump proxy config to current docker state, returns
# new proxy config
patch "/proxy/" do
Log.info { "Updating routing" }
# Get all the funkos, create routes for them all
update_proxy_config
end
def self.update_proxy_config
docker_api = Docr::API.new(Docr::Client.new)
containers = docker_api.containers.list(all: true)
funkos = [] of String
containers.each { |container|
names = container.names.select &.starts_with? "/faaso-"
next if names.empty?
funkos << names[0][7..]
}
funkos.sort!
config = %(
Port 8888
Listen 0.0.0.0
Timeout 600
Allow 0.0.0.0/0
ReverseOnly Yes
ReverseMagic Yes
ReversePath "/admin/" "http://127.0.0.1:3000/"
) + funkos.map { |funko| %(ReversePath "/faaso/#{funko}/" "http://#{funko}:3000/") }.join("\n")
if @@current_config != config
File.open("tinyproxy.conf", "w") do |file|
file << config
end
# Reload config
Process.run(command: "/usr/bin/killall", args: ["-USR1", "tinyproxy"])
@@current_config = config
end
config
end
end
# Update proxy config once a second
spawn do
loop do
Proxy.update_proxy_config
sleep 1.second
end
end

View File

@ -1,44 +0,0 @@
require "kemal"
SECRETS = Hash(String, String).new
SECRET_PATH = "./secrets/"
# TODO: sanitize all inputs
# Store secrets in a tree of files
def update_secrets
# Save new secrets
SECRETS.map do |_name, value|
funko, name = _name.split("-", 2)
funko_dir = Path.new(SECRET_PATH, funko)
Dir.mkdir_p(funko_dir)
File.write(Path.new(funko_dir, name), value)
end
# Delete secrets not in the hash
Dir.glob(Path.new(SECRET_PATH, "*")).each do |funko_dir|
funko = File.basename(funko_dir)
Dir.glob(Path.new(funko_dir, "*")).each do |secret_file|
name = File.basename(secret_file)
unless SECRETS.has_key?("#{funko}-#{name}")
File.delete(secret_file)
end
end
end
end
# Gets a secret in form {"name": "funko_name-secret_name", "value": "secret_value"}
post "/secrets/" do |env|
name = env.params.json["name"].as(String)
value = env.params.json["value"].as(String)
SECRETS[name] = value
update_secrets
halt env, status_code: 201, response: "Created"
end
# Deletes a secret from the disk and memory
delete "/secrets/:name/" do |env|
name = env.params.url["name"]
SECRETS.delete(name)
update_secrets
halt env, status_code: 204, response: "Deleted"
end

View File

@ -1,7 +1,5 @@
require "./daemon-secrets.cr"
require "./daemon-proxyconf.cr"
require "compress/gzip"
require "crystar"
require "compress/gzip"
require "docr"
require "kemal-basic-auth"
require "kemal"
@ -10,6 +8,51 @@ require "uuid"
# FIXME: make configurable
basic_auth "admin", "admin"
current_config = File.read("tinyproxy.conf")
# Get current proxy config
get "/proxy/" do
current_config
end
# Bump proxy config to current docker state, returns
# new proxy config
patch "/proxy/" do
Log.info { "Updating routing" }
# Get all the funkos, create routes for them all
docker_api = Docr::API.new(Docr::Client.new)
containers = docker_api.containers.list(all: true)
funkos = [] of String
containers.each { |container|
names = container.names.select &.starts_with? "/faaso-"
next if names.empty?
funkos << names[0][7..]
}
funkos.sort!
proxy_config = %(
Port 8888
Listen 0.0.0.0
Timeout 600
Allow 0.0.0.0/0
ReverseOnly Yes
ReverseMagic Yes
ReversePath "/admin/" "http://127.0.0.1:3000/"
) + funkos.map { |funko| %(ReversePath "/faaso/#{funko}/" "http://#{funko}:3000/") }.join("\n")
if current_config != proxy_config
File.open("tinyproxy.conf", "w") do |file|
file << proxy_config
end
# Reload config
Process.run(command: "/usr/bin/killall", args: ["-USR1", "tinyproxy"])
current_config = proxy_config
end
proxy_config
end
# Bring up the funko
get "/funko/:name/up/" do |env|
name = env.params.url["name"]

View File

@ -1,9 +1,9 @@
Port 8888
Listen 0.0.0.0
Timeout 600
Allow 0.0.0.0/0
ReverseOnly Yes
ReverseMagic Yes
ReversePath "/admin/" "http://127.0.0.1:3000/"
Port 8888
Listen 0.0.0.0
Timeout 600
Allow 0.0.0.0/0
ReverseOnly Yes
ReverseMagic Yes
ReversePath "/admin/" "http://127.0.0.1:3000/"
ReversePath "/faaso/hello/" "http://hello:3000/"