lib/mongo/auth/aws/conversation.rb
# frozen_string_literal: true
# rubocop:todo all
# Copyright (C) 2020 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Mongo
module Auth
class Aws
# Defines behavior around a single MONGODB-AWS conversation between the
# client and server.
#
# @see https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst#mongodb-aws
#
# @api private
class Conversation < SaslConversationBase
# Continue the AWS conversation. This sends the client final message
# to the server after setting the reply from the previous server
# communication.
#
# @param [ BSON::Document ] reply_document The reply document of the
# previous message.
# @param [ Server::Connection ] connection The connection being
# authenticated.
#
# @return [ Protocol::Message ] The next message to send.
def continue(reply_document, connection)
@conversation_id = reply_document[:conversationId]
payload = reply_document[:payload].data
payload = BSON::Document.from_bson(BSON::ByteBuffer.new(payload))
@server_nonce = payload[:s].data
validate_server_nonce!
@sts_host = payload[:h]
unless (1..255).include?(@sts_host.bytesize)
raise Error::InvalidServerAuthConfiguration, "STS host name length is not in 1..255 bytes range: #{@sts_host}"
end
selector = CLIENT_CONTINUE_MESSAGE.merge(
payload: BSON::Binary.new(client_final_payload),
conversationId: conversation_id,
)
build_message(connection, user.auth_source, selector)
end
private
# @return [ String ] The server nonce.
attr_reader :server_nonce
# Get the id of the conversation.
#
# @return [ Integer ] The conversation id.
attr_reader :conversation_id
def client_first_data
{
r: BSON::Binary.new(client_nonce),
p: 110,
}
end
def client_first_payload
client_first_data.to_bson.to_s
end
def wrap_data(data)
BSON::Binary.new(data.to_bson.to_s)
end
def client_nonce
@client_nonce ||= SecureRandom.random_bytes(32)
end
def client_final_payload
credentials = CredentialsRetriever.new(user).credentials
request = Request.new(
access_key_id: credentials.access_key_id,
secret_access_key: credentials.secret_access_key,
session_token: credentials.session_token,
host: @sts_host,
server_nonce: server_nonce,
)
# Uncomment this line to validate obtained credentials on the
# client side prior to sending them to the server.
# This generally produces informative diagnostics as to why
# the credentials are not valid (e.g., they could be expired)
# whereas the server normally does not elaborate on why
# authentication failed (but the reason usually is logged into
# the server logs).
#
# Note that credential validation requires that the client is
# able to access AWS STS. If this is not permitted by firewall
# rules, validation will fail but credentials may be perfectly OK
# and the server may be able to authenticate using them just fine
# (provided the server is allowed to communicate with STS).
#request.validate!
payload = {
a: request.authorization,
d: request.formatted_time,
}
if credentials.session_token
payload[:t] = credentials.session_token
end
payload.to_bson.to_s
end
end
end
end
end