Skip to content

Commit

Permalink
Merge pull request #3 from GrantBirki/env-off-by-default
Browse files Browse the repository at this point in the history
make it so `enable_env_lookup` is disabled by default
  • Loading branch information
GrantBirki authored Oct 15, 2024
2 parents d190421 + ad10118 commit b5bfe2c
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 7 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,29 +157,31 @@ export MY_CLIENT_HMAC_SECRET_BLUE="my_secret_1"
export MY_CLIENT_HMAC_SECRET_GREEN="my_secret_2"
```

Then simply calling `hmac_auth()` in your kemal application will automatically configure the middleware with the client names and secrets from the environment variables. Here is how it works:
Then simply calling `hmac_auth(enable_env_lookup: true)` in your kemal application will automatically configure the middleware with the client names and secrets from the environment variables. Here is how it works:

1. When the `hmac_auth()` method is called with no arguments, the middleware will look for environment variables that start with the client name in all caps and end with `HMAC_SECRET_BLUE` or `HMAC_SECRET_GREEN` (these are called the `HMAC_KEY_SUFFIX_LIST` and can be further configured with environment variables as well). For example, if the client name is `my_client`, the middleware will look for an environment variable called `MY_CLIENT_HMAC_SECRET_BLUE` or `MY_CLIENT_HMAC_SECRET_GREEN`.
1. When the `hmac_auth()` method is called with the `enable_env_lookup: true` argument, the middleware will look for environment variables that start with the client name in all caps and end with `HMAC_SECRET_BLUE` or `HMAC_SECRET_GREEN` (these are called the `HMAC_KEY_SUFFIX_LIST` and can be further configured with environment variables as well). For example, if the client name is `my_client`, the middleware will look for an environment variable called `MY_CLIENT_HMAC_SECRET_BLUE` or `MY_CLIENT_HMAC_SECRET_GREEN`.
2. If one or more matching secrets are found for the client name, the middleware will be configured with the client name and the secrets.
3. The client name and secrets will be used to generate the HMAC token for incoming requests.
4. The first matching secret for the client that successfully generates a valid HMAC token will be used to authenticate the request.

Here is an example passing no params into `hmac_auth()` and letting it self-hydrate from the environment variables:
Here is an example:

```crystal
# file: hmac_server.cr
require "kemal"
require "kemal-hmac"
# Initialize the HMAC middleware with no params so it can self-hydrate from the environment variables
hmac_auth()
# Initialize the HMAC middleware with the 'enable_env_lookup: true' param so it can self-hydrate from the environment variables
hmac_auth(enable_env_lookup: true)
# Now all endpoints are protected with HMAC authentication
get "/" do |env|
"Hi, %s! You sent a request that was successfully verified with HMAC auth using environment variables" % env.kemal_authorized_client?
end
```

> Note: The `enable_env_lookup: true` argument is optional and defaults to `false`. If you do not pass this argument, you will need to pass the `hmac_secrets` argument to the `hmac_auth` method to configure the middleware. This is the desired way to configure the middleware in production as it is more explicit, less error-prone, and performs significantly better than using environment variables.
## Configuration

This section goes into detail on the configuration options available for the `kemal-hmac` middleware and the client utility.
Expand Down Expand Up @@ -221,7 +223,8 @@ hmac_auth(
rejected_message_prefix: "Unauthorized:",
hmac_key_suffix_list: ["HMAC_SECRET_BLUE", "HMAC_SECRET_GREEN"],
hmac_key_delimiter: "_",
hmac_algorithm: "SHA256"
hmac_algorithm: "SHA256",
enable_env_lookup: false
)
# ... kemal logic here
Expand Down
30 changes: 29 additions & 1 deletion spec/kemal-hmac/kemal-hmac_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ describe "Kemal::Hmac" do
it "uses a custom handler and fails due to a failed secret regex match" do
hmac_handler = SpecAuthHandler.new(
hmac_secrets: {} of String => Array(String),
enable_env_lookup: true
)
request = HTTP::Request.new(
"GET",
Expand Down Expand Up @@ -370,7 +371,7 @@ describe "Kemal::Hmac" do
it "successfully flows through and passes when fetching secrets from the ENV" do
client = "Octo1-Client_prod"
secret = "super-secret"
hmac_handler = Kemal::Hmac::Handler.new
hmac_handler = Kemal::Hmac::Handler.new(enable_env_lookup: true)
hmac_client = Kemal::Hmac::Client.new(client, secret, "SHA256")
headers = hmac_client.generate_headers("/api")

Expand All @@ -392,4 +393,31 @@ describe "Kemal::Hmac" do
response.status_code.should eq 404
context.kemal_authorized_client?.should eq(client)
end

it "fails to flow through when fetching secrets from the env since it is disabled by default" do
client = "Octo1-Client_prod"
secret = "super-secret"
hmac_handler = Kemal::Hmac::Handler.new
hmac_client = Kemal::Hmac::Client.new(client, secret, "SHA256")
headers = hmac_client.generate_headers("/api")

ENV["#{client.upcase}_HMAC_SECRET_BLUE"] = "unset"
ENV["#{client.upcase}_HMAC_SECRET_GREEN"] = secret

request = HTTP::Request.new(
"GET",
"/api",
headers: HTTP::Headers{
"hmac-client" => headers["hmac-client"],
"hmac-timestamp" => headers["hmac-timestamp"],
"hmac-token" => headers["hmac-token"],
},
)

io, context = create_request_and_return_io_and_context(hmac_handler, request)
response = HTTP::Client::Response.from_io(io, decompress: false)
response.status_code.should eq 401
response.body.should contain "Unauthorized: no secrets found for client: Octo1-Client_prod"
context.kemal_authorized_client?.should eq(nil)
end
end
2 changes: 2 additions & 0 deletions src/kemal-hmac.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def hmac_auth(
hmac_key_suffix_list : Array(String)? = nil,
hmac_key_delimiter : String? = nil,
hmac_algorithm : String? = nil,
enable_env_lookup : Bool = false,
)
add_handler Kemal.config.hmac_handler.new(
hmac_secrets: hmac_secrets,
Expand All @@ -44,5 +45,6 @@ def hmac_auth(
hmac_key_suffix_list: hmac_key_suffix_list,
hmac_key_delimiter: hmac_key_delimiter,
hmac_algorithm: hmac_algorithm,
enable_env_lookup: enable_env_lookup,
)
end
6 changes: 6 additions & 0 deletions src/kemal-hmac/handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Kemal::Hmac
# hmac_key_suffix_list: ["HMAC_SECRET_BLUE", "HMAC_SECRET_GREEN"] - only used for env variable lookups
# hmac_key_delimiter: "_" - only used for env variable lookups
# hmac_algorithm: "SHA256"
# enable_env_lookup: true (this value is set to false by default)
def initialize(
hmac_secrets : Hash(String, Array(String)) = {} of String => Array(String),
hmac_client_header : String? = nil,
Expand All @@ -39,6 +40,7 @@ module Kemal::Hmac
hmac_key_suffix_list : Array(String)? = nil,
hmac_key_delimiter : String? = nil,
hmac_algorithm : String? = nil,
enable_env_lookup : Bool = false,
)
@hmac_client_header = hmac_client_header || HMAC_CLIENT_HEADER
@hmac_timestamp_header = hmac_timestamp_header || HMAC_TIMESTAMP_HEADER
Expand All @@ -49,6 +51,7 @@ module Kemal::Hmac
@hmac_key_suffix_list = hmac_key_suffix_list || HMAC_KEY_SUFFIX_LIST
@hmac_key_delimiter = hmac_key_delimiter || HMAC_KEY_DELIMITER
@hmac_algorithm = fetch_hmac_algorithm(hmac_algorithm)
@enable_env_lookup = enable_env_lookup

@required_hmac_headers = [
@hmac_client_header,
Expand Down Expand Up @@ -170,6 +173,9 @@ module Kemal::Hmac
return @secrets_cache[key]
end

# exit early if env lookups are explicitly disabled
return [] of String unless @enable_env_lookup

unless KEY_VALIDATION_REGEX.match(key)
raise InvalidFormatError.new("client name must only contain letters, numbers, -, or _")
end
Expand Down

0 comments on commit b5bfe2c

Please sign in to comment.