Class: Msf::WebServices::JsonRpcApp

Inherits:
Sinatra::Base
  • Object
show all
Defined in:
lib/msf/core/web_services/json_rpc_app.rb

Defined Under Namespace

Classes: BootError

Constant Summary collapse

MIN_API_TOKEN_LENGTH =

Shortest static API token accepted from the environment.

16
REMEDY =

Appended to every BootError so the operator knows both ways out.

'Set MSF_WS_JSON_RPC_API_TOKEN to a token of at least ' \
"#{MIN_API_TOKEN_LENGTH} characters, which needs no database, " \
"or run 'msfdb init' and create a user to authenticate with database API tokens.".freeze
REMEDY_TOKEN_ONLY =

Appended when only the static token route is available.

'Set MSF_WS_JSON_RPC_API_TOKEN to a token of at least ' \
"#{MIN_API_TOKEN_LENGTH} characters.".freeze
REMEDY_NO_USER_TOKEN =

Appended when the database holds users but none of them have an API token. Creating a user does not issue one - report_user leaves persistence_token unset - so this is a reachable state rather than a theoretical one.

"Issue one with 'POST /api/v1/auth/generate-token' on the data service, " \
'or set MSF_WS_JSON_RPC_API_TOKEN to a token of at least ' \
"#{MIN_API_TOKEN_LENGTH} characters instead.".freeze

Class Method Summary collapse

Class Method Details

.blank_token?(token) ⇒ Boolean

Returns true if the token is missing or contains no usable characters.

Returns:

  • (Boolean)

    true if the token is missing or contains no usable characters.



145
146
147
# File 'lib/msf/core/web_services/json_rpc_app.rb', line 145

def self.blank_token?(token)
  !token.is_a?(String) || token.strip.empty?
end

.boot!Object

Initialise auth and DB state once at startup. Called from the warmup block in msf-json-rpc.ru, after the framework is ready but before the socket is bound and connections are accepted.

A database is not required, but authentication is. It is resolved from either:

1. a static token supplied via MSF_WS_JSON_RPC_API_TOKEN
2. API tokens belonging to users in the database, when it is reachable

If neither is available the service refuses to start, rather than accepting unauthenticated requests.

Only option 2 needs a database to authenticate against. Which option is in use says nothing about whether a database is connected: the framework connects regardless of the token, so db.* works alongside a static token whenever a database is configured.

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/msf/core/web_services/json_rpc_app.rb', line 84

def self.boot!
  # Build the framework up front so that connecting to the database, and any failure
  # doing so, happens here rather than on the first request to reach the service.
  settings.framework

  token = settings.api_token
  unless blank_token?(token)
    if token.length < MIN_API_TOKEN_LENGTH
      raise BootError, 'JSON-RPC server cannot start: MSF_WS_JSON_RPC_API_TOKEN must be at least ' \
                       "#{MIN_API_TOKEN_LENGTH} characters."
    end

    return
  end

  # A blank token would otherwise authenticate any request presenting an equally
  # blank token, so discard it and fall back to database authentication.
  settings.api_token = nil

  # User accounts of a remote data service live on that service, not locally, so its
  # API tokens cannot be validated here. The local database that Active Record requires
  # alongside it holds a different set of users, and authenticating against those would
  # grant access on credentials the operator never issued for this service.
  unless settings.data_service_url.nil? || settings.data_service_url.empty?
    raise BootError, 'JSON-RPC server cannot start: no API token is configured and API tokens cannot ' \
                     "be validated against the remote data service #{settings.data_service_url}. " \
                     "#{REMEDY_TOKEN_ONLY}"
  end

  db = settings.framework.db
  unless db.active
    raise BootError, 'JSON-RPC server cannot start: no API token is configured and the database is ' \
                     "not available. #{REMEDY}"
  end

  begin
    users = db.users({})
    # A nil list means no data service is registered at all, which is indistinguishable
    # here from an empty users table and reported the same way.
    tokens = users.nil? ? [] : users.map(&:persistence_token)
  rescue StandardError => e
    raise BootError, 'JSON-RPC server cannot start: no API token is configured and the database ' \
                     "could not be queried (#{e.class}: #{e.message}). #{REMEDY}"
  end

  if tokens.empty?
    raise BootError, 'JSON-RPC server cannot start: no API token is configured and the database ' \
                     "holds no users. #{REMEDY}"
  end

  # Existing users are not enough: this application only authenticates persistence_token,
  # so a database of password-only accounts would start a service that 401s every request.
  if tokens.all? { |user_token| blank_token?(user_token) }
    raise BootError, 'JSON-RPC server cannot start: no API token is configured and no user in the ' \
                     "database has an API token to authenticate with. #{REMEDY_NO_USER_TOKEN}"
  end

  settings.db_manager = db
end

.setup_default_middleware(builder) ⇒ Object



190
191
192
193
194
195
# File 'lib/msf/core/web_services/json_rpc_app.rb', line 190

def self.setup_default_middleware(builder)
  super
  # Insertion at pos 1 needed to immediately follow Sinatra::ExtendedBase
  # proc block identical to one used in 'use' method lib/rack/builder:86
  builder.instance_variable_get(:@use).insert(1, proc { |app| JsonRpcExceptionHandling::RackMiddleware.new(app) })
end