Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API #1155

Merged
merged 20 commits into from Dec 31, 2022
Merged

REST API #1155

merged 20 commits into from Dec 31, 2022

Conversation

jamilbk
Copy link
Member

@jamilbk jamilbk commented Nov 29, 2022

TODO

@github-actions github-actions bot added the kind/feature New feature or request label Nov 29, 2022
@coveralls
Copy link

coveralls commented Nov 29, 2022

Pull Request Test Coverage Report for Build 174f75ed524e202f38e3cf567f226c8c6f274cdc-PR-1155

  • 318 of 413 (77.0%) changed or added relevant lines in 68 files are covered.
  • 9 unchanged lines in 6 files lost coverage.
  • Overall coverage increased (+0.9%) to 67.048%

Changes Missing Coverage Covered Lines Changed/Added Lines %
apps/fz_http/lib/fz_http.ex 0 1 0.0%
apps/fz_http/lib/fz_http/api_tokens/api_token.ex 6 7 85.71%
apps/fz_http/lib/fz_http/application.ex 0 1 0.0%
apps/fz_http/lib/fz_http/configurations.ex 16 17 94.12%
apps/fz_http/lib/fz_http/devices.ex 13 14 92.86%
apps/fz_http/lib/fz_http/devices/device.ex 29 30 96.67%
apps/fz_http/lib/fz_http/devices/stats_updater.ex 0 1 0.0%
apps/fz_http/lib/fz_http/oidc/refresher.ex 1 2 50.0%
apps/fz_http/lib/fz_http/users.ex 1 2 50.0%
apps/fz_http/lib/fz_http_web/auth/html/error_handler.ex 1 2 50.0%
Files with Coverage Reduction New Missed Lines %
apps/fz_http/lib/fz_http/devices/device.ex 1 93.94%
apps/fz_http/lib/fz_http/devices/device_setting.ex 1 80.0%
apps/fz_http/lib/fz_http/oidc/start_proxy.ex 1 6.67%
apps/fz_http/lib/fz_http_web/error_helpers.ex 1 92.86%
apps/fz_http/lib/fz_http_web/live/setting_live/oidc_form_component.ex 2 93.55%
apps/fz_http/lib/fz_http/saml/start_proxy.ex 3 4.17%
Totals Coverage Status
Change from base Build 963aca75cbfbd070c8693e4b4c564c6fc3bfb312: 0.9%
Covered Lines: 1290
Relevant Lines: 1924

💛 - Coveralls

@jamilbk jamilbk changed the title rest api REST API Dec 14, 2022
@jamilbk jamilbk force-pushed the feat/rest-api branch 3 times, most recently from d0fc3a3 to 791b180 Compare December 21, 2022 12:01
@jamilbk jamilbk closed this Dec 24, 2022
@jamilbk jamilbk reopened this Dec 24, 2022
jamilbk added a commit that referenced this pull request Dec 26, 2022
* Adds views for managing ApiTokens
* ApiTokens have a max count of 10 per user
* JWT is generated on the fly when "viewing" an ApiToken. The JWT is
never stored in the DB

There are still a few things left for the REST API. Will update #1155
with the TODOs.


https://user-images.githubusercontent.com/167144/209414501-1d7ebdba-15fa-4d6f-9072-4ed6bf7dd1b5.mov
@jamilbk jamilbk force-pushed the feat/rest-api branch 2 times, most recently from 1f7a9bd to 4947f66 Compare December 26, 2022 14:13
* Add RevokeToken

* Checkpoint UUID ApiToken

* Passing tests

* github restore cache

* Fix on_delete behavior

Remove unneeded functions, add tests

REST API: Controllers and Views (#1148)

* Checkpoint controllers and views

* Checkpoint Guardian pipeline

* Fix tests

* Add more JSON views

* Create APICase template

* Fix partial name

* Checkpoint device api tests

* Checkpoint tests

* Add Rule controller tests

Add revoked helpers to ApiTokens

Remove unneeded yarn.lock; update package-lock.json

Refactors all tests to minimize global state (#1192)

Tests were intermittently failing for two reasons:

* We were modifying the Application env with `restore_env/3` (a custom
  function) between tests. This is not thread-safe.
* The Conf cache was also being modified between tests. This is a global
  memory store and is also not thread-safe.

This PR aims to mock both of these using Mox in order to provide for a
manageable way to test the repurcussions of both Application env and Cache
calls.

Make default IP range CGNAT; randomize IP numbering (#1200)

~~Allocating IP addresses for device creation is not a trivial
endeavor.~~

~~Before, we were using a window function and full-table lock in order
to select a candidate device IP to use when creating a new device. This
behavior was designed to mimic DHCP.~~

~~Consider the following example.~~

~~Say you currently have a list of devices in the database with the
following IPs:~~

~~10.0.0.1
10.0.0.3
10.0.0.100
10.0.0.101~~

~~Assuming WIREGUARD_IPV4_NETWORK is set to 10.0.0.0/24, the IP address
for the next device generated _should_ be 10.0.0.2. Before, this IP
would have been found with a large SELECT query that tried to find the
first non-sequential IP address in the devices table. This required
wrapping the SELECT and following INSERT in a single transaction to
ensure the IP address "reserved" would be insertable.~~

~~As you may imagine, this resulted in deadlocks, and prevented us from
running much of the device tests with `async: true` enabled.~~

~~@conectado came up with a simple but elegant solution to this problem
using a Mutex to guard an IP allocation cache. It works as follows:~~

~~1. Maintain an allocation cache: a list of all of the available IP
    addresses represented as a list of {gap_start, gap_end} tuples.
    Using the example data above, this would be:
    ```
    [
      {10.0.0.2, 10.0.0.2},
      {10.0.0.4, 10.0.0.99},
      {10.0.0.102, 10.0.0.254}
    ]
    ```
2. When creating a new device, lock the mutex, then select the first
    available IP address from the allocation cache. Increment the IP
    address and save this new state as the new allocation cache.
3. If allocation fails (i.e. the cache is empty), rebuild the cache from
    the DB. This has the nice effect that a stale cache just skips a few
    IPs and causes a premature cache rebuild, but doesn't otherwise halt
    creation like a `SEQUENCE` field would.
4. If the rebuilt cache is **also** empty, the IP address pool actually
    *has* been exhausted, so return `nil` for the IP. The Device schema
interprets this as an error and adds an appropriate error to the
changeset.~~

**EDIT**: After further research into Postgres gapless sequences and the
information theory realities the problem is bound by, I'm not so sure
creating a global mutex to maintain strict consistency for sequential IP
allocation is the right approach.

Going to explore some simpler methods of IP allocation, likely involving
random selection inside the CGNAT range.

Co-authored-by: Andrew Dryga <andrew@dryga.com>

Remove line committed by accident (#1231)

Replace all bigint primary keys with UUIDs (#1233)
@jamilbk jamilbk marked this pull request as ready for review December 26, 2022 23:12
@jamilbk
Copy link
Member Author

jamilbk commented Dec 26, 2022

@AndrewDryga This is ready for final review before going into master. Was dealing with some weird Docker issue where only a subset of my node_modules were being copied over. Removed the masked builddir hack from the docker-compose.yml file to alleviate. EDIT: Looks like a missing line in the Dockerfile.dev to pull in a nested local_modules dir. Not sure why that never caused an issue until now

The remaining items in the TODO will be handled in separate PRs.

jamilbk and others added 4 commits December 26, 2022 21:57
Co-authored-by: Andrew Dryga <andrew@dryga.com>
Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
What a time to be a developer, right?
"""
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@ip_regex ~r/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a nice way to write long regexes especially if they repeatedly use the same patterns (like IP and CIDR would be almost the same, plus address segments are the same):

iex(1)> seg = "[0-9]*"                                                                                                                                                                                             "[0-9]*"                                                                                                                                                                                                           iex(2)> Regex.compile!(seg)
~r/[0-9]*/
iex(3)> Regex.compile!(seg <> seg)
~r/[0-9]*[0-9]*/

So you can define module attributes like @ipv4_address_segment and @ipv6_address_segment and then reuse them multiple time in ip/cidr regexes reducing their complexity greately.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ this is just FYI, it's a lot of work to split this monster now and make sure it's valid

def create_user_api_token(%FzHttp.Users.User{} = user, attrs) do
changeset =
attrs
|> Enum.into(%{user_id: user.id})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just pass it to the changeset and use put_assoc there. This will make the changeset code more explicit like we did here in the context. It looks even nicer because we have a changeset function per action, so their argument is different and the update changeset won't even accept the user anymore as the user can't be changed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later having attributes like this one will be handy so we can use them for auth checks, eg. if auth subject has permission to manage user tokens.

Comment on lines +45 to +46
# At the moment it's not a big concern. Fixing it would require locking against INSERTs or DELETEs
# while counts are happening.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave a TODO I think it's better to leave a better design suggestion if somebody decided to tackle this.

Locking the entire ApiTokens table would be really bad if we have a large environment or cloud panel for multiple clients, instead, we can lock the user.id record in users table preventing all user changes as a side effect but then the lock won't affect other users using the table.

Suggested change
# At the moment it's not a big concern. Fixing it would require locking against INSERTs or DELETEs
# while counts are happening.
# At the moment it's not a big concern. Fixing it would require locking against user record in `users` table
# while counts are happening.

This removes the ETS-backed cache entirely. Caches are tricky to scale
horizontally and are likely not needed at this point in our product
roadmap.

We can revisit at a later point when performance becomes an issue.

Fixes #1240
@AndrewDryga AndrewDryga mentioned this pull request Dec 27, 2022
AndrewDryga
AndrewDryga previously approved these changes Dec 27, 2022
field :openid_connect_providers, :map
field :saml_identity_providers, :map
field :openid_connect_providers, :map, read_after_writes: true
field :saml_identity_providers, :map, read_after_writes: true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to read them after writing? Won't the result be the same?

AndrewDryga
AndrewDryga previously approved these changes Dec 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants