Secrets REST API
This commit is contained in:
parent
ae328f465c
commit
bed7bcf6f3
@ -1,9 +1,9 @@
|
||||
# This configuration file was generated by `ameba --gen-config`
|
||||
# on 2024-06-30 18:10:12 UTC using Ameba version 1.6.1.
|
||||
# on 2024-07-01 13:11:14 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: 10
|
||||
# Problems found: 11
|
||||
# Run `ameba --only Documentation/DocumentationAdmonition` for details
|
||||
Documentation/DocumentationAdmonition:
|
||||
Description: Reports documentation admonitions
|
||||
@ -12,6 +12,7 @@ Documentation/DocumentationAdmonition:
|
||||
- src/faaso.cr
|
||||
- src/daemon.cr
|
||||
- src/funko.cr
|
||||
- src/daemon-secrets.cr
|
||||
- spec/faaso_spec.cr
|
||||
Admonitions:
|
||||
- TODO
|
||||
|
@ -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,4 +18,32 @@ 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.
|
||||
|
||||
## Problem 2: how can the proxy know the secrets without keeping them in the image?
|
||||
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.
|
||||
|
44
src/daemon-secrets.cr
Normal file
44
src/daemon-secrets.cr
Normal file
@ -0,0 +1,44 @@
|
||||
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
|
@ -1,5 +1,6 @@
|
||||
require "crystar"
|
||||
require "./daemon-secrets.cr"
|
||||
require "compress/gzip"
|
||||
require "crystar"
|
||||
require "docr"
|
||||
require "kemal-basic-auth"
|
||||
require "kemal"
|
||||
|
Loading…
Reference in New Issue
Block a user