Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- [API Gateway] New custom metric: `rig_proxy_requests_total`. For details see [`metrics-details.md`](docs/metrics-details.md). [#157](https://github.com/Accenture/reactive-interaction-gateway/issues/157)
- [Rig] Integrity-check for the correlation ID [#218](https://github.com/Accenture/reactive-interaction-gateway/pull/218)
- _Beta_ - Added Apache Avro support for consumer and producer as well as Kafka Schema Registry.
- [Docs] Added new set of topics in documentation about Api Gateway, even streams and scaling.
- [Docs] Added examples section to documentation website.
Expand Down Expand Up @@ -40,17 +41,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- When using the proxy, RIG will now add an additional [`Forwarded` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded).
[#113](https://github.com/Accenture/reactive-interaction-gateway/issues/113)
[#113](https://github.com/Accenture/reactive-interaction-gateway/issues/113)
- Increased length of header value in HTTP requests to 16384 to support long tokens for SAML.

### Changed

- HTTPS certificates may now be passed using absolute paths. (Previously, the locations of the HTTPS certificates were limited to the OTP-applications' `priv` directories `rig_api/priv/cert` and `rig_inbound_gateway/priv/cert`.) Additionally, for security reasons we no longer include the self-signed certificate with the docker image. Please adapt your environment configuration accordingly.
[#151](https://github.com/Accenture/reactive-interaction-gateway/issues/151)
[#182](https://github.com/Accenture/reactive-interaction-gateway/issues/182)
[#151](https://github.com/Accenture/reactive-interaction-gateway/issues/151)
[#182](https://github.com/Accenture/reactive-interaction-gateway/issues/182)
- Validation errors for SSE & WS connections and the subscriptions endpoint should now be a lot more helpful. Invalid JWTs, as well as invalid subscriptions, cause the endpoints to respond with an error immediately.
[#54](https://github.com/Accenture/reactive-interaction-gateway/issues/54)
[#164](https://github.com/Accenture/reactive-interaction-gateway/issues/164)
[#54](https://github.com/Accenture/reactive-interaction-gateway/issues/54)
[#164](https://github.com/Accenture/reactive-interaction-gateway/issues/164)

### Fixed

Expand Down
4 changes: 4 additions & 0 deletions apps/rig/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ config :rig, Rig.EventStream.KafkaToHttp,
targets: {:system, :list, "FIREHOSE_KAFKA_HTTP_TARGETS", []},
group_id: {:system, "KAFKA_GROUP_ID", "rig"}

config :rig, Rig.Connection.Codec,
codec_secret_key: {:system, "NODE_COOKIE", nil},
codec_default_key: "7tsf4Y6GTOfPY1iDo4PqZA=="

config :porcelain, driver: Porcelain.Driver.Basic

import_config "#{Mix.env()}.exs"
56 changes: 49 additions & 7 deletions apps/rig/lib/rig/connection/codec.ex
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
defmodule Rig.Connection.Codec do
@moduledoc """
Encode and decode a connection token, e.g., for correlation.

TODO: sign the token - using binary_to_term without signature verification is a threat.
"""
use Rig.Config, [:codec_secret_key]

@doc "Turn a pid into an url-encoded string."
@spec serialize(pid) :: binary
def serialize(pid) do
conf = config()
secret_key = conf.codec_secret_key || conf.codec_default_key
pid
|> :erlang.term_to_binary()
|> encrypt(secret_key)
|> Base.url_encode64()

# TODO sign this
end

# ---

@doc "Convert a serialized string back into a pid."
@spec deserialize(binary) :: {:ok, pid} | {:error, :not_base64 | :invalid_term}
def deserialize(base64_encoded) do
# TODO check signature here

conf = config()
secret_key = conf.codec_secret_key || conf.codec_default_key
with {:ok, decoded_binary} <- decode64(base64_encoded) do
binary_to_term(decoded_binary)
decoded_binary
|> decrypt(secret_key)
|> binary_to_term()
end
end

Expand All @@ -38,6 +40,46 @@ defmodule Rig.Connection.Codec do

# ---

@doc """
The function processes any string or number and returns exactly 16 byte binary.
The hash produced by this function can be used as key by both encrypt and decrypt functions.
"""
@spec hash(binary) :: binary
def hash(key) do
:crypto.hash(:md5, :erlang.term_to_binary(key))
end

@doc """
Encrypts value using key and returns init. vector, ciphertag (MAC) and ciphertext concatenated.
Additional authenticated data (AAD) adds parameters like protocol version num. to MAC
"""
@aad "AES256GCM"
@spec encrypt(binary, binary) :: binary
def encrypt(val, key) do
mode = :aes_gcm
secret_key = hash(key)
init_vector = :crypto.strong_rand_bytes(16)
{ciphertext, ciphertag} =
:crypto.block_encrypt(mode, secret_key, init_vector, {@aad, to_string(val), 16})
init_vector <> ciphertag <> ciphertext
end

# ---

@doc """
Decrypts ciphertext using key.
Ciphertext is init. vector, ciphertag (MAC) and the actual ciphertext concatenated.
"""
@spec decrypt(binary, binary) :: binary
def decrypt(ciphertext, key) do
mode = :aes_gcm
secret_key = hash(key)
<<init_vector::binary-16, tag::binary-16, ciphertext::binary>> = ciphertext
:crypto.block_decrypt(mode, secret_key, init_vector, {@aad, ciphertext, tag})
end

# ---

@spec decode64(binary()) :: {:ok, binary()} | {:error, :not_base64}
defp decode64(base64) do
case Base.url_decode64(base64) do
Expand Down
79 changes: 78 additions & 1 deletion apps/rig/lib/rig/connection/codec_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Rig.ConnectionCodecTest do

use ExUnitProperties

import Rig.Connection.Codec, only: [serialize: 1, deserialize!: 1]
import Rig.Connection.Codec, only: [serialize: 1, deserialize!: 1, encrypt: 2, decrypt: 2]

property "Serialize and deserialize invert each other" do
# `check all id <- integer(0..0x7FFF)` does not work on a single node.
Expand All @@ -29,4 +29,81 @@ defmodule Rig.ConnectionCodecTest do
refute :c.pid(id, serial, creation) |> serialize() |> String.contains?(bad_chars)
end
end

property "Encryption / decryption works for key \"magiccookie\"" do
id = 0
key = "magiccookie"

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end

property "Encryption / decryption works for key of empty string" do
id = 0
key = ""

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end

property "Encryption / decryption works for key \"a\"" do
id = 0
key = "a"

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end

property "Encryption / decryption works for a very long key (90 bytes)" do
id = 0
key = "QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234"

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end

property "Encryption / decryption works for key of integer 0" do
id = 0
key = 0

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end

property "Encryption / decryption works for key of negative integer" do
id = 0
key = -123

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end

property "Encryption / decryption works for key of large integer" do
id = 0
key = 12_345_678_901_234_567_890

check all serial <- integer(0..0x1FFF),
creation <- integer(0..0x03) do
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
assert pid == pid |> encrypt(key) |> decrypt(key)
end
end
end
2 changes: 1 addition & 1 deletion docs/rig-ops-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Variable&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
`KINESIS_OTP_JAR` | Path to the `OtpErlang.jar` file that contains the `JInterface` implementation. If left empty, RIG picks the file from its Erlang environment (Erlang must be compiled with Java support enabled). | nil
`KINESIS_STREAM` | The name of the Kinesis stream to consume. | "RIG-outbound"
`LOG_LEVEL` | Controls logging level for RIG, available values are: "debug", "info", "warn", "error". Production is using "warn" level. | :warn
`NODE_COOKIE` | Erlang cookie used in distributed mode, so nodes in cluster can communicate between each other. | nil
`NODE_COOKIE` | Erlang cookie used in distributed mode, so nodes in cluster can communicate between each other.<br />Used also as secret key for integrity-check of correlation IDs. | nil
`NODE_HOST` | Erlang hostname for given node, used to build Erlang long-name `rig@NODE_HOST`. This value is used by Erlang's distributed mode, so nodes can see each other. | nil
`PROXY_CONFIG_FILE` | Configuration JSON file with initial API definition for API Proxy. Use this variable to pass either a path to a JSON file, or the JSON string itself. A path can be given in absolute or in relative form (e.g., `proxy/your_json_file.json`). If given in relative form, the working directory is one of RIG's `priv` dirs (e.g., `/opt/sites/rig/lib/rig_inbound_gateway-2.0.2/priv/` in a Docker container). | nil
`PROXY_RECV_TIMEOUT` | Timeout used when receiving a response for a forwarded/proxied request. | 5000
Expand Down