Rails의 라우팅과 constraints를 이용하여 IP기반 ACL 만들기

Rails Application에서 접근제어를 하는 방법은 여러가지가 있습니다. 예전에 포스팅하기도 헀고, 튜토리얼에서도 기본적으로 이야기되는 HTTP Auth를 이용한 인증방식 부터 세션, 로그인 기반 인증까지 여러가지 형태로 구현이 가능하죠. 오늘은 가장 기본적인 접근제어인 IP 기반 ACL에 대한 이야기를 할까 합니다.

IP 기반 ACL?

많은 서버, 장비, Application 들이 ID/PW 기반 인증과 함께 IP ACL도 사용합니다. 그만큼 IP ACL은 간단하면서도 중첩됬을 떄 효율이 좋은 편이라 그런 것 같습니다.

제 블로그를 보시고 계신다면, 잘 아시는 개념이겠지만 한번 더 이야기하자면 white list(특정 IP만 접근), black list(특정 IP는 접근 불가) 형태로 IP, IP 대역별 접근 제어를 통해서 불필요한 접근이나 공격을 어느정도 막아줄 수 있습니다.

Rails에서 ACL?

어떤 기능이나 정책을 만들 때 항상 라이브러리를 먼저 찾게 됩니다. 잘 활용한다면 쉽고 안정적이게 프로그램을 만들 수 있으니 대체로 찾아보는 습관이 든 것 같습니다. Rails에선 ACL을 어떻게 구현하고, 어떤 라이브러리 들이 있을까 찾아보았습니다.

대표적으로 Rails-auth와 함꼐 여러가지 라이브러리 들이 쓰이고 있었습니다.

acl9 - Acl9 is a role-based authorization system that provides a concise DSL for securing your Rails application.
AccessGranted - Multi-role and whitelist based authorization gem for Rails.
Authority - ORM-neutral way to authorize actions in your Rails app.
CanCanCan - Continuation of CanCan, an authorization Gem for Ruby on Rails.
Declarative Authorization - An authorization Rails plugin using a declarative DSL for specifying authorization rules in one place.
Petergate - Easy to use and read action and content based authorizations.
Pundit - Minimal authorization through OO design and pure Ruby classes.

http://awesome-ruby.com/#awesome-ruby-authorization

우선 Rails Auth는 인증서를 기반으롸고, 유저 단위로 관리되는 ACL 입니다. 가장 먼저 관심을 가져서.. 여러가지 시도해봤지만 IP ACL을 직접적으로 지원해주진 않았습니다. (이걸로 글을 써볼까 하고 작성헀던 데이터들이.. 아쉬움과 함께 뭍혀갑니다. / 어차피 귀찮아서 안쓸거면서..)

다른 라이브러리들도 user base 인건 비슷비슷합니다…

그래서 고민하던 찰나, 예전에 Rails 튜토리얼 사이트에서 봤던 내용이 하나 생각났습니다. constraints 부분이였는데.. 이걸로 IP ACL이 가능할 것 같았습니다.

그래서 바로 타이핑을 시작했지요.

Write route.rb with constraints!

Rails에서 route.rb는 라우팅 규칙을 정의하는 코드입니다. 여기에 작성된 코드대로 Rails Application이 접근하는 페이지를 각각의 Controller로 매핑시켜 줍니다.

보편적으로.. 이런식으로 많이 쓰죠.

get '/posts/:id', to: 'posts#show'

posts/ID값 타입으로 GET 요청을 받으면 post controller의 show로 넘겨라! 실제로 GET 요청으로 이러한 요청을 받으면 Rails는 post로 넘겨서 내용을 보여줄 수 있도록 하지요.

GET /posts/17 HTTP/1.1
Host: 127.0.0.1

그리고 모든 기능과 페이지를 하나하나 작성할 순 없으니.. 보통

resources :posts

이런식으로 리소스로 묶어줍니다. 이렇게 리소스로 한번에 처리하면 REST API 기준 CRUD 요청은 자동으로 매핑됩니다.

다시 본론으로 와서… constraints를 활용하면 라우팅 규칙에 제한을 둘 수 있는데 이를 IP주소와 비교해서 처리하면 그게 곧 IP 기반 ACL이 됩니다.

Rails.application.routes.draw do
  constraints(ip:"127.0.0.1") do     # ip가 127.0.0.1 인 경우에 아래 resources를 라우팅함
  resources :checks, :testers, :todos
  end
end

코드를 보면 127.0.0.1, 즉 localhost에서 접근한 요청만 resource로 매핑시켜주고 이게 아닌 경우 라우팅이 정의되지 않아 없는 페이지로 나타날겁니다. 테스틀 해보면..

  1. localhost(127.0.0.1)에서 접근
Started GET "/checks" for 127.0.0.1 at 2018-04-29 13:16:39 +0900
Processing by ChecksController#index as HTML
  Rendering checks/index.html.erb within layouts/application
  1. 다른host에서 접근
Started GET "/checks" for 192.168.0.14 at 2018-04-29 13:19:01 +0900
Cannot render console from 192.168.0.14! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255

ActionController::RoutingError (No route matches [GET] "/checks"):

이런식으로 localhost에서만 정상적으로 페이지가 로드됩니다.

추가로 개별 리소스에 대해서 제한을 걸 땐 리소스 내부에서 constraints으로 규칙을 걸어주면 됩니다.

resources :testers, constraints: { ip: /192\.168\.\d+\.\d+/ }
# testers 관련 요청은 192.168 대역에서만 접근이 가능하도록 ...