Ruby용 서버 스켈레톤

기본 요건

서버 구현에 필요한 보석은 다음과 같습니다.

  • google-protobuf (이 튜토리얼에 사용된 3.2.X)
  • gRPC (이 튜토리얼에 사용된 1.2.X)

서비스 정의를 다운로드하고 다음 디렉터리 구조를 만듭니다.

[base_dir]
├── certificates
├── lib
├── protos
    └── booking_service.proto
└── server.rb

인터페이스 설명에서 Ruby 라이브러리를 생성합니다.

$ cd [base_dir]
$ grpc_tools_ruby_protoc -I protos --ruby_out=lib --grpc_out=lib protos/booking_service.proto

서버 구현

스켈레톤 구현:

#!/usr/bin/ruby2.0

# Copyright 2017, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Sample gRPC server that implements the BookingService and enforces mutual
# authentication.
#
# Usage: $ path/to/server.rb

this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'lib')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)

require 'grpc'
require 'grpc/health/checker'
require 'grpc/health/v1/health_services_pb'
require 'openssl'
require 'optparse'
require 'booking_service_services_pb.rb'

# Peer certificate validation that checks for a specific CN in the subject
# name. Add additional valid CNs, e.g. for a test client, to the array.
ACCEPTED_CNS = ["mapsbooking.businesslink-3.net"]
def check_peer_cert(grpc_call)
  valid_cert = false
  certificate = OpenSSL::X509::Certificate.new grpc_call.peer_cert
  certificate.subject().to_a().each do |name_entry|
    if (name_entry[0] == "CN") && ACCEPTED_CNS.include?(name_entry[1])
      valid_cert = true
    end
  end
  unless valid_cert
    fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::UNAUTHENTICATED,
                             "Client cert has invalid CN")
  end
end


# BookingServer is a simple server that implements the BookingService.
class BookingServer < Ext::Maps::Booking::Partner::V0::BookingService::Service

  PartnerApi = Ext::Maps::Booking::Partner::V0

  def initialize(peer_cert_validator)
    @peer_cert_validator = peer_cert_validator
  end

  def create_lease(lease_req, grpc_call)
    @peer_cert_validator.call(grpc_call)
    lease = lease_req.lease.dup
    # Perform availability check etc.
    #
    # For error conditions (example):
    #  fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
    #                           "Slot unavailable")
    #
    # Happy path: populate the response
    # [...]
    # Assign a lease ID
    # lease.lease_id = ...
    PartnerApi::CreateLeaseResponse.new(lease: lease)
  end

  def create_booking(booking_req, grpc_call)
    @peer_cert_validator.call(grpc_call)
    booking = PartnerApi::Booking.new
    # Populate booking
    # [...]
    # Assign a booking ID
    # booking.booking_id = ...
    PartnerApi::CreateBookingResponse.new(booking: booking)
  end

  def update_booking(booking_req, grpc_call)
    @peer_cert_validator.call(grpc_call)
    booking = PartnerApi::Booking.new
    # * Look up the booking with ID booking_req.booking.booking_id
    # * Update according to the request and return the updated booking.
    PartnerApi::UpdateBookingResponse.new(booking: booking)
  end

end


# Loads the certificates for the test server.
def load_server_certs
  this_dir = File.expand_path(File.dirname(__FILE__))
  cert_dir = File.join(this_dir, 'certificates')
  # In order:
  # * PEM file with trusted root certificates for client auth
  # * Server private key
  # * PEM file with server certificate chain
  files = ['trusted_client_roots.pem', 'server.key', 'server.pem']
  files.map { |f| File.open(File.join(cert_dir, f)).read }
end


# Creates ServerCredentials from certificates.
def server_credentials
  certs = load_server_certs
  GRPC::Core::ServerCredentials.new(
      certs[0], [{private_key: certs[1], cert_chain: certs[2]}], true)
end


def main
  # Parse command line arguments
  disable_tls = false
  OptionParser.new do |opts|
    opts.on('--disable_tls',
            'true to disable TLS. NOT FOR PRODUCTION USE!') do |v|
      disable_tls = v
    end
  end.parse!

  s = GRPC::RpcServer.new
  # Listen on port 50051 on all interfaces. Update for production use.
  s.add_http2_port('[::]:50051',
                   disable_tls ? :this_port_is_insecure : server_credentials)

  cert_validator = disable_tls ? ->(grpc_call) {} : method(:check_peer_cert)
  s.handle(BookingServer.new cert_validator)

  health_checker = Grpc::Health::Checker.new
  health_checker.add_status(
      "ext.maps.booking.partner.v0.BookingService",
      Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING)
  s.handle(health_checker)
  s.run_till_terminated
end

if __FILE__ == $0
  main()
end

TLS를 사용하지 않고 서버 테스트

초기 테스트에서는 TLS를 사용 중지할 수 있습니다.

$ cd [base_dir]
$ ruby server.rb --disable_tls

프로덕션 용도로는 적합하지 않습니다.

프로덕션 인증서 구성

서버에서 TLS를 사용 설정하려면 다음 파일이 필요합니다.

  • certificates/server.pem: 서버의 PEM 형식 인증서 체인
  • certificates/server.key: 서버 인증서 체인의 비공개 키입니다.
  • certificates/trusted_client_roots.pem: 클라이언트를 인증할 때 신뢰할 수 있는 루트 인증서

클라이언트를 인증할 때 신뢰할 수 있는 클라이언트 루트 인증서 세트가 사용됩니다. Mozilla와 같은 기관에서 신뢰할 수 있는 이 루트 세트를 가져오거나 현재 Google Internet Authority G2에서 권장하는 루트 세트를 설치할 수 있습니다. 후자의 경우 루트 인증서를 수동으로 업데이트해야 할 수도 있습니다.

최종 디렉터리 구조

[base_dir]
├── certificates
    ├── server.pem
    ├── server.key
    └── trusted_client_roots.pem
├── lib
    ├── booking_service_pb.rb
    └── booking_service_services_pb.rb
├── protos
    └── booking_service.proto
└── server.rb