기본 요건
서버 구현에 필요한 보석은 다음과 같습니다.
- 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