add security to all admin endpoints

This commit is contained in:
Rick Watson 2019-05-29 23:48:16 +01:00
parent 71e5830d6c
commit 73433586b6
24 changed files with 264 additions and 230 deletions

View File

@ -47,7 +47,7 @@ export function redirectingAuthorizedFetch(url, params) {
return new Promise(function (resolve, reject) {
authorizedFetch(url, params).then(response => {
if (response.status === 401) {
history.go("/");
history.push("/");
} else {
resolve(response);
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import { withNotifier } from '../components/SnackbarNotification';
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
/*
* It is unlikely this application will grow complex enough to require redux.
*
@ -42,7 +42,7 @@ export const restComponent = (endpointUrl, FormComponent) => {
fetched: false,
errorMessage: null
});
fetch(endpointUrl)
redirectingAuthorizedFetch(endpointUrl)
.then(response => {
if (response.status === 200) {
return response.json();
@ -58,7 +58,7 @@ export const restComponent = (endpointUrl, FormComponent) => {
saveData(e) {
this.setState({ fetched: false });
fetch(endpointUrl, {
redirectingAuthorizedFetch(endpointUrl, {
method: 'POST',
body: JSON.stringify(this.state.data),
headers: new Headers({

View File

@ -5,6 +5,7 @@ import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/E
import SectionContent from '../components/SectionContent';
import WiFiNetworkSelector from '../forms/WiFiNetworkSelector';
import {withNotifier} from '../components/SnackbarNotification';
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
const NUM_POLLS = 10
const POLLING_FREQUENCY = 500
@ -38,7 +39,7 @@ class WiFiNetworkScanner extends Component {
scanNetworks() {
this.pollCount = 0;
this.setState({scanningForNetworks:true, networkList: null, errorMessage:null});
fetch(SCAN_NETWORKS_ENDPOINT).then(response => {
redirectingAuthorizedFetch(SCAN_NETWORKS_ENDPOINT).then(response => {
if (response.status === 202) {
this.schedulePollTimeout();
return;
@ -70,7 +71,7 @@ class WiFiNetworkScanner extends Component {
}
pollNetworkList() {
fetch(LIST_NETWORKS_ENDPOINT)
redirectingAuthorizedFetch(LIST_NETWORKS_ENDPOINT)
.then(response => {
if (response.status === 200) {
return response.json();

View File

@ -1,6 +1,6 @@
#include <APSettingsService.h>
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) {
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) {
onConfigUpdated();
}

View File

@ -19,11 +19,11 @@
#define AP_SETTINGS_FILE "/config/apSettings.json"
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
class APSettingsService : public SettingsService {
class APSettingsService : public AdminSettingsService {
public:
APSettingsService(AsyncWebServer* server, FS* fs);
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~APSettingsService();
void loop();

View File

@ -6,6 +6,10 @@ void ArduinoJsonJWT::setSecret(String secret){
_secret = secret;
}
String ArduinoJsonJWT::getSecret(){
return _secret;
}
/*
* ESP32 uses mbedtls, ESP2866 uses bearssl.
*

View File

@ -31,6 +31,8 @@ public:
ArduinoJsonJWT(String secret);
void setSecret(String secret);
String getSecret();
String buildJWT(JsonObject &payload);
void parseJWT(String jwt, JsonDocument &jsonDocument);
};

View File

@ -1,52 +0,0 @@
#ifndef AsyncAuthJsonWebHandler_H_
#define AsyncAuthJsonWebHandler_H_
#include <ESPAsyncWebServer.h>
#include <AsyncJsonWebHandler.h>
#include <ArduinoJson.h>
#include <SecurityManager.h>
typedef std::function<void(AsyncWebServerRequest *request, JsonDocument &jsonDocument, Authentication &authentication)> AuthenticationJsonRequestCallback;
/**
* Extends AsyncJsonWebHandler with a wrapper which verifies the user is authenticated.
*
* TODO - Extend with role checking support, possibly with a callback to verify the user.
*/
class AsyncAuthJsonWebHandler: public AsyncJsonWebHandler {
private:
SecurityManager *_securityManager;
using AsyncJsonWebHandler::onRequest;
public:
AsyncAuthJsonWebHandler() :
AsyncJsonWebHandler(), _securityManager(NULL) {}
~AsyncAuthJsonWebHandler() {}
void setSecurityManager(SecurityManager *securityManager) {
_securityManager = securityManager;
}
void onRequest(AuthenticationJsonRequestCallback callback) {
AsyncJsonWebHandler::onRequest([this, callback](AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
if(!_securityManager) {
Serial.print("Security manager not configured for endpoint: ");
Serial.println(_uri);
request->send(500);
return;
}
Authentication authentication = _securityManager->authenticateRequest(request);
if (!authentication.isAuthenticated()) {
request->send(401);
return;
}
callback(request, jsonDocument, authentication);
});
}
};
#endif // end AsyncAuthJsonWebHandler_H_

View File

@ -6,7 +6,7 @@ AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityMan
_signInHandler.setUri(SIGN_IN_PATH);
_signInHandler.setMethod(HTTP_POST);
_signInHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE);
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
_signInHandler.onRequest(std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2));
server->addHandler(&_signInHandler);
}
@ -31,7 +31,7 @@ void AuthenticationService::signIn(AsyncWebServerRequest *request, JsonDocument
Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.isAuthenticated()) {
User* user = authentication.getUser();
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE);
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_AUTHENTICATION_SIZE);
JsonObject jsonObject = response->getRoot();
jsonObject["access_token"] = _securityManager->generateJWT(user);
response->setLength();

View File

@ -2,6 +2,9 @@
#define AuthenticationService_H_
#include <SecurityManager.h>
#include <ESPAsyncWebServer.h>
#include <AsyncJsonWebHandler.h>
#include <AsyncArduinoJson6.h>
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
#define SIGN_IN_PATH "/rest/signIn"

View File

@ -1,6 +1,6 @@
#include <NTPSettingsService.h>
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, NTP_SETTINGS_SERVICE_PATH, NTP_SETTINGS_FILE) {
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, NTP_SETTINGS_SERVICE_PATH, NTP_SETTINGS_FILE) {
#if defined(ESP8266)
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1));

View File

@ -17,11 +17,11 @@
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
class NTPSettingsService : public SettingsService {
class NTPSettingsService : public AdminSettingsService {
public:
NTPSettingsService(AsyncWebServer* server, FS* fs);
NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~NTPSettingsService();
void loop();

View File

@ -1,6 +1,6 @@
#include <OTASettingsService.h>
OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, OTA_SETTINGS_SERVICE_PATH, OTA_SETTINGS_FILE) {
OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, OTA_SETTINGS_SERVICE_PATH, OTA_SETTINGS_FILE) {
#if defined(ESP8266)
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
#elif defined(ESP_PLATFORM)

View File

@ -19,11 +19,11 @@
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
class OTASettingsService : public SettingsService {
class OTASettingsService : public AdminSettingsService {
public:
OTASettingsService(AsyncWebServer* server, FS* fs);
OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~OTASettingsService();
void loop();

View File

@ -1,41 +1,5 @@
#include <SecurityManager.h>
SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, SECURITY_SETTINGS_PATH, SECURITY_SETTINGS_FILE) {}
SecurityManager::~SecurityManager() {}
void SecurityManager::readFromJsonObject(JsonObject& root) {
// secret
_jwtSecret = root["jwt_secret"] | DEFAULT_JWT_SECRET;
_jwtHandler.setSecret(_jwtSecret);
// users
_users.clear();
if (root["users"].is<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) {
_users.push_back(User(user["username"], user["password"], user["admin"]));
}
}
}
void SecurityManager::writeToJsonObject(JsonObject& root) {
// secret
root["jwt_secret"] = _jwtSecret;
// users
JsonArray users = root.createNestedArray("users");
for (User _user : _users) {
JsonObject user = users.createNestedObject();
user["username"] = _user.getUsername();
user["password"] = _user.getPassword();
user["admin"] = _user.isAdmin();
}
}
void SecurityManager::begin() {
readFromFS();
}
Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) {
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) {
@ -90,3 +54,15 @@ String SecurityManager::generateJWT(User *user) {
populateJWTPayload(payload, user);
return _jwtHandler.buildJWT(payload);
}
ArRequestHandlerFunction SecurityManager::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest *request){
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
}
onRequest(request);
};
}

View File

@ -2,27 +2,16 @@
#define SecurityManager_h
#include <list>
#include <SettingsService.h>
#include <DNSServer.h>
#include <IPAddress.h>
#include <ArduinoJsonJWT.h>
#include <ESPAsyncWebServer.h>
#define DEFAULT_JWT_SECRET "esp8266-react"
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
#define AUTHORIZATION_HEADER "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
#define MAX_JWT_SIZE 128
#define MAX_SECURITY_MANAGER_SETTINGS_SIZE 512
#define SECURITY_MANAGER_MAX_USERS 5
#define MAX_USERS_SIZE 1024
class User {
private:
@ -62,15 +51,25 @@ class Authentication {
}
};
class SecurityManager : public SettingsService {
typedef std::function<boolean(Authentication &authentication)> AuthenticationPredicate;
class AuthenticationPredicates {
public:
static bool NONE_REQUIRED(Authentication &authentication) {
return true;
};
static bool IS_AUTHENTICATED(Authentication &authentication) {
return authentication.isAuthenticated();
};
static bool IS_ADMIN(Authentication &authentication) {
return authentication.isAuthenticated() && authentication.getUser()->isAdmin();
};
};
class SecurityManager {
public:
SecurityManager(AsyncWebServer* server, FS* fs);
~SecurityManager();
void begin();
/*
* Authenticate, returning the user if found
*/
@ -86,21 +85,17 @@ class SecurityManager : public SettingsService {
*/
String generateJWT(User *user);
/**
* Wrap the provided request to provide validation against an AuthenticationPredicate.
*/
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
private:
// jwt handler
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
// access point settings
String _jwtSecret;
std::list<User> _users;
// endpoint functions
void fetchUsers(AsyncWebServerRequest *request);
private:
/*
* Lookup the user by JWT

View File

@ -0,0 +1,35 @@
#include <SecuritySettingsService.h>
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : AdminSettingsService(server, fs, this, SECURITY_SETTINGS_PATH, SECURITY_SETTINGS_FILE), SecurityManager() {}
SecuritySettingsService::~SecuritySettingsService() {}
void SecuritySettingsService::readFromJsonObject(JsonObject& root) {
// secret
_jwtHandler.setSecret(root["jwt_secret"] | DEFAULT_JWT_SECRET);
// users
_users.clear();
if (root["users"].is<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) {
_users.push_back(User(user["username"], user["password"], user["admin"]));
}
}
}
void SecuritySettingsService::writeToJsonObject(JsonObject& root) {
// secret
root["jwt_secret"] = _jwtHandler.getSecret();
// users
JsonArray users = root.createNestedArray("users");
for (User _user : _users) {
JsonObject user = users.createNestedObject();
user["username"] = _user.getUsername();
user["password"] = _user.getPassword();
user["admin"] = _user.isAdmin();
}
}
void SecuritySettingsService::begin() {
readFromFS();
}

View File

@ -0,0 +1,26 @@
#ifndef SecuritySettingsService_h
#define SecuritySettingsService_h
#include <SettingsService.h>
#include <SecurityManager.h>
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
class SecuritySettingsService : public AdminSettingsService, public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer* server, FS* fs);
~SecuritySettingsService();
void begin();
protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
};
#endif // end SecuritySettingsService_h

View File

@ -9,54 +9,19 @@
#include <AsyncTCP.h>
#endif
#include <SecurityManager.h>
#include <SettingsPersistence.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <AsyncJsonWebHandler.h>
#include <AsyncArduinoJson6.h>
/*
* Abstraction of a service which stores it's settings as JSON in a file system.
*/
class SettingsService : public SettingsPersistence {
private:
AsyncJsonWebHandler _updateHandler;
void fetchConfig(AsyncWebServerRequest *request){
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE);
JsonObject jsonObject = response->getRoot();
writeToJsonObject(jsonObject);
response->setLength();
request->send(response);
}
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
if (jsonDocument.is<JsonObject>()){
JsonObject newConfig = jsonDocument.as<JsonObject>();
readFromJsonObject(newConfig);
writeToFS();
// write settings back with a callback to reconfigure the wifi
AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}, MAX_SETTINGS_SIZE);
JsonObject jsonObject = response->getRoot();
writeToJsonObject(jsonObject);
response->setLength();
request->send(response);
} else {
request->send(400);
}
}
protected:
// will serve setting endpoints from here
AsyncWebServer* _server;
// implement to perform action when config has been updated
virtual void onConfigUpdated(){}
public:
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath):
@ -79,6 +44,81 @@ private:
readFromFS();
}
protected:
// will serve setting endpoints from here
AsyncWebServer* _server;
AsyncJsonWebHandler _updateHandler;
virtual void fetchConfig(AsyncWebServerRequest *request) {
// handle the request
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE);
JsonObject jsonObject = response->getRoot();
writeToJsonObject(jsonObject);
response->setLength();
request->send(response);
}
virtual void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
// handle the request
if (jsonDocument.is<JsonObject>()){
JsonObject newConfig = jsonDocument.as<JsonObject>();
readFromJsonObject(newConfig);
writeToFS();
// write settings back with a callback to reconfigure the wifi
AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}, MAX_SETTINGS_SIZE);
JsonObject jsonObject = response->getRoot();
writeToJsonObject(jsonObject);
response->setLength();
request->send(response);
} else {
request->send(400);
}
}
// implement to perform action when config has been updated
virtual void onConfigUpdated(){}
};
class AdminSettingsService : public SettingsService {
public:
AdminSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath):
SettingsService(server, fs, servicePath, filePath), _securityManager(securityManager) {
}
protected:
// will validate the requests with the security manager
SecurityManager* _securityManager;
void fetchConfig(AsyncWebServerRequest *request) {
// verify the request against the predicate
Authentication authentication = _securityManager->authenticateRequest(request);
if (!getAuthenticationPredicate()(authentication)) {
request->send(401);
return;
}
// delegate to underlying implemetation
SettingsService::fetchConfig(request);
}
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
// verify the request against the predicate
Authentication authentication = _securityManager->authenticateRequest(request);
if (!getAuthenticationPredicate()(authentication)) {
request->send(401);
return;
}
// delegate to underlying implemetation
SettingsService::updateConfig(request, jsonDocument);
}
// override to override the default authentication predicate, IS_ADMIN
AuthenticationPredicate getAuthenticationPredicate() {
return AuthenticationPredicates::IS_ADMIN;
}
};
#endif // end SettingsService

View File

@ -1,8 +1,12 @@
#include <WiFiScanner.h>
WiFiScanner::WiFiScanner(AsyncWebServer *server) : _server(server) {
_server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1));
_server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1));
WiFiScanner::WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager) : _server(server) {
_server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)
);
_server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN)
);
}
void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) {

View File

@ -13,6 +13,7 @@
#include <ArduinoJson.h>
#include <AsyncArduinoJson6.h>
#include <TimeLib.h>
#include <SecurityManager.h>
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
@ -23,7 +24,7 @@ class WiFiScanner {
public:
WiFiScanner(AsyncWebServer *server);
WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager);
private:

View File

@ -1,6 +1,6 @@
#include <WiFiSettingsService.h>
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {}
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {}
WiFiSettingsService::~WiFiSettingsService() {}

View File

@ -7,11 +7,11 @@
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
class WiFiSettingsService : public SettingsService {
class WiFiSettingsService : public AdminSettingsService {
public:
WiFiSettingsService(AsyncWebServer* server, FS* fs);
WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~WiFiSettingsService();
void begin();

View File

@ -11,7 +11,7 @@
#include <FS.h>
#include <SecurityManager.h>
#include <SecuritySettingsService.h>
#include <WiFiSettingsService.h>
#include <APSettingsService.h>
#include <NTPSettingsService.h>
@ -27,15 +27,14 @@
AsyncWebServer server(80);
SecurityManager securityManager = SecurityManager(&server, &SPIFFS);
SecuritySettingsService securitySettingsService = SecuritySettingsService(&server, &SPIFFS);
WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS, &securitySettingsService);
APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS, &securitySettingsService);
NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS, &securitySettingsService);
OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS, &securitySettingsService);
AuthenticationService authenticationService = AuthenticationService(&server, &securitySettingsService);
WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS);
APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS);
NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS);
OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS);
AuthenticationService authenticationService = AuthenticationService(&server, &securityManager);
WiFiScanner wifiScanner = WiFiScanner(&server);
WiFiScanner wifiScanner = WiFiScanner(&server, &securitySettingsService);
WiFiStatus wifiStatus = WiFiStatus(&server);
NTPStatus ntpStatus = NTPStatus(&server);
APStatus apStatus = APStatus(&server);
@ -48,8 +47,8 @@ void setup() {
Serial.begin(SERIAL_BAUD_RATE);
SPIFFS.begin();
// start security manager
securityManager.begin();
// start security settings service first
securitySettingsService.begin();
// start services
ntpSettingsService.begin();