Turn SSL verification off for JGit clone command - jgit

I am trying to a clone of a Git Repository via the CloneCommand.
With this piece of code
`Git.cloneRepository().setDirectory(new File(path)).setURI(url).call();`
The remote repository is on a GitBlit Instance which uses self signed certificates.
Because of these self signed certificates I get the below exception when the Fetch Part of the Clone is executing:
Caused by: java.security.cert.CertificateException: No name matching <hostName> found
at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:221)
at sun.security.util.HostnameChecker.match(HostnameChecker.java:95)
While I could create a new TrustManager, register a dummy HostnameVerifier and create and init a SSLContext that uses this dummy TrustManager.
And after the clone is done revert all of this.
However this would mean that any other SSL connection that is initiated during the same time would expose them to unsecured connections.
On a already cloned repo you can set the http.sslVerify to false and JGit works perfectly fine.
Is there a cleaner way in which I could tell JGit to set this http.sslVerify to false for Clone action, like I can do for a already cloned repo.

With version 4.9, JGit will handle SSL verification more gracefully. If the SSL
handshake was unsuccessful, JGit will ask the CredentialsProvider whether SSL verification should be skipped or not.
In this process, the CredentialsProvider is given an InformationalMessage describing the issue textually and up to three YesNoType CredentialItems to decide whether to skip SSL verification for this operation, for the current repository, and/or always.
It seems that the change was made with an interactive UI in mind and it might be hard to answer these 'credential requests' programmatically. The commit message of this change describes the behavior in more detail.
If you are certain that SSL verification is the only InformationalMessage that will be sent, you can apply the technique used in this test that accompanies the change and blindly answer 'yes' to all such questions.
For earlier versions of JGit, or if the CredentialsProvider model does not fit your needs, there are two workarounds described below.
To work around this limitation, you can execute the specific clone steps manually as suggested in the comments below:
init a repository using the InitCommand
set ssl verify to false
StoredConfig config = git.getRepository().getConfig();
config.setBoolean( "http", null, "sslVerify", false );
config.save();
fetch (see FetchCommand)
checkout (see CheckoutCommand)
Another way to work around the issue is to provide an HttpConnectionFactory that returns HttpConnections with dummy host name and certificate verifiers. For example:
class InsecureHttpConnectionFactory implements HttpConnectionFactory {
#Override
public HttpConnection create( URL url ) throws IOException {
return create( url, null );
}
#Override
public HttpConnection create( URL url, Proxy proxy ) throws IOException {
HttpConnection connection = new JDKHttpConnectionFactory().create( url, proxy );
HttpSupport.disableSslVerify( connection );
return connection;
}
}
HttpConnection is in package org.eclipse.jgit.transport.http and is a JGit abstraction for HTTP connections. While the example uses the default implementation (backed by JDK http code), you are free to use your own implementation or the one provided by the org.eclipse.jgit.transport.http.apache package that uses Apache http components.
The currently used connection factory can be changed with HttpTransport::setConnectionFactory():
HttpConnectionFactory preservedConnectionFactory = HttpTransport.getConnectionFactory();
HttpTransport.setConnectionFactory( new InsecureHttpConnectionFactory() );
// clone repository
HttpTransport.setConnectionFactory( preservedConnectionFactory );
Unfortunately, the connection factory is a singleton so that this trick needs extra work (e.g. a thread local variable to control if sslVerify is on or off) when JGit commands are executed concurrently.

Another workaround is to create a .gitconfig file in the home of the current user before calling Git.cloneRepository():
File file = new File(System.getProperty("user.home")+"/.gitconfig");
if(!file.exists()) {
PrintWriter writer = new PrintWriter(file);
writer.println("[http]");
writer.println("sslverify = false");
writer.close();
}
This will make JGit skip SSL certificate verification.

I have inferred from all answers above for the snippet below;
private void disableSSLVerify(URI gitServer) throws Exception {
if (gitServer.getScheme().equals("https")) {
FileBasedConfig config = SystemReader.getInstance().openUserConfig(null, FS.DETECTED);
synchronized (config) {
config.load();
config.setBoolean(
"http",
"https://" + gitServer.getHost() + ':' + (gitServer.getPort() == -1 ? 443 : gitServer.getPort()),
"sslVerify", false);
config.save();
}
}
}
This option is safer because it allows sslVerify to false for the gitServer alone.
Please take a look at this link which shares other options.

I have come across with the same problem and I used ChainingCredentialsProvider class to create a credential provider, I did my implementation as bellow,
Please note that this is an implementation of a previously given answer.
CredentialsProvider token = new UsernamePasswordCredentialsProvider("PRIVATE-TOKEN", token);
CredentialsProvider ssl = new CredentialsProvider() {
#Override
public boolean supports(CredentialItem... items) {
for ( CredentialItem item : items ) {
if ( (item instanceof CredentialItem.YesNoType) ) {
return true;
}
}
return false;
}
#Override
public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem {
for ( CredentialItem item : items ) {
if ( item instanceof CredentialItem.YesNoType ) {
(( CredentialItem.YesNoType ) item).setValue(true);
return true;
}
}
return false;
}
#Override
public boolean isInteractive() {
return false;
}
};
CredentialsProvider cp = new ChainingCredentialsProvider(ssl, token);
Git result = Git.cloneRepository()
.setURI(gitProjectUrl)
.setDirectory(localPath)
.setCredentialsProvider(cp)
.call();

Related

Could not create SSL/TLS secure channel when connecting through WSS, only on Azure

I am building a Web Api (using ASP.NET Web API), that connects via Secure WebSockets to an endpoint that our client exposed (wss://client-domain:4747/app/engineData). They gave me their certificates all in .pem format (root.pem and client.pem), and a private key (client_key.pem).
In order to get this done I did the following:
1) Converted client.pem and client_key.pem to a single .pfx file (used this here: Convert a CERT/PEM certificate to a PFX certificate)
2) I used the library System.Net.WebSockets, and wrote the following code:
private void InitWebSockesClient()
{
client = new ClientWebSocket();
client.Options.SetRequestHeader(HEADER_KEY, HEADER_VALUE); //Some headers I need
AddCertificatesSecurity();
}
private void AddCertificatesSecurity()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12;
// I KNOW THIS SHOULDNT BE USED ON PROD, had to use it to make it
// work locally.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
X509Certificate2 x509 = new X509Certificate2();
// this is the pfx I converted from client.pem and client_key
byte[] rawData = ReadFile(certificatesPath + #"\cert.pfx");
x509.Import(rawData, "123456", X509KeyStorageFlags.UserKeySet);
X509Certificate2Collection certificateCollection = new X509Certificate2Collection(x509);
client.Options.ClientCertificates = certificateCollection;
}
And when I want to connect I call:
public async Task<bool> Connect()
{
Uri uriToConnect = new Uri(URL);
await client.ConnectAsync(uriToConnect, CancellationToken.None);
return client.State == WebSocketState.Open;
}
This works fine locally. But whenever I deploy my Web Api on Azure (App Service) and make an HTTP request to it, it throws:
System.Net.WebSockets.WebSocketException - Unable to connect to the remote server.
And the inner exception:
System.Net.WebException - The request was aborted: Could not create SSL/TLS secure channel.
I enabled WebSockets on the AppService instance.
If I delete the line that always return true for the certificate validation, it doesn't work even locally, and the message says something like:
The remote certificate is invalid according to the validation procedure.
So definitely I got something wrong with the certificates, those three .pem files are being used right now in a similar [![enter image description here][1]][1]app in a node.js and work fine, the WSS connection is established properly. I don't really know what usage give to each one, so I am kind of lost here.
These are the cipher suites of the domain I want to connect: https://i.stack.imgur.com/ZFbo3.png
Inspired by Tom's comment, I finally made it work by just adding the certificate to the Web App in Azure App Service, instead of trying to use it from the filesystem. First I uploaded the .pfx file in the SSL Certificates section in Azure. Then, in the App settings, I added a setting called WEBSITE_LOAD_CERTIFICATES, with the thumbprint of the certificate I wanted (the .pfx).
After that, I modified my code to do work like this:
private void InitWebSockesClient()
{
client = new ClientWebSocket();
client.Options.SetRequestHeader(HEADER_KEY, HEADER_VALUE); //Some headers I need
AddCertificateToWebSocketsClient();
}
private void AddCertificateToWebSocketsClient()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12;
// this should really validate the cert
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// reading cert from store
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection =
certStore.Certificates.Find(X509FindType.FindByThumbprint,
CERTIFICATES_THUMBPRINT,
false);
if (certCollection.Count > 0)
{
client.Options.ClientCertificates = certCollection;
}
else
{
// handle error
}
certStore.Close();
}
Where CERTIFICATES_THUMBPRINT is a string (thumbsprint of your certificate, the one you saw on Azure).
In case you want to make it work locally, you just need to install the certificate on your computer, as otherwise it won't obviously find it on the store.
Reference for all this in Azure docs: https://learn.microsoft.com/en-us/azure/app-service/app-service-web-ssl-cert-load.

Adding security to RESTful API [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 6 years ago.
Improve this question
I am wanting to implement two websites that need to communicate with each other. (Since one of the sites has a separate deployment for each customer, and is spread across many servers, sharing a database or communicating privately is not an option.) So I've been looking into RESTful APIs.
Unfortunately, I'm running into a lot of information that I'm not familiar with. One issue is security. We don't need anything fancy--we're not a bank or anything. I think we can just get away with HTTPS and a basic username and password.
Questions:
How would I pass the username and password to the API? Would they just be passed as bare arguments in the URL?
Does .NET provide any mechanism for authorizing such username and passwords, or do I just manually see if the password is in our database on each and every request? (I would hash for security.)
How would I pass the username and password to the API? Would they just
be passed as bare arguments in the URL?
It can be either in the URL or in the header. If you are using HTTPS, it will all be encrypted so it will not be bare. Please see this for more details.
Does .NET provide any mechanism for authorizing such username and
passwords, or do I just manually see if the password is in our
database on each and every request? (I would hash for security.)
No you do not need to check the database on every request. You can check once, create a token with an expiry and the client can keep sending you the token. This way you do not have to keep checking the database every single time.
Please see see this answer for some helpful information.
I think basic authentication with base64 encoding will be sufficient. If not you can always change it. Here are the different ways to apply it to your backend code:
To apply an authentication filter to a controller, decorate the controller class with the filter attribute. The following code sets the [IdentityBasicAuthentication] filter on a controller class, which enables Basic Authentication for all of the controller's actions.
[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
public IHttpActionResult Get() { . . . }
public IHttpActionResult Post() { . . . }
}
To apply the filter to one action, decorate the action with the filter. The following code sets the [IdentityBasicAuthentication] filter on the controller's Post method.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
public IHttpActionResult Get() { . . . }
[IdentityBasicAuthentication] // Enable Basic authentication for this action.
public IHttpActionResult Post() { . . . }
}
To apply the filter to all Web API controllers, add it to GlobalConfiguration.Filters.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new IdentityBasicAuthenticationAttribute());
// Other configuration code not shown...
}
}
Finally here is an example of the implementation, you may change it as you need:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using BasicAuthentication.Results;
namespace BasicAuthentication.Filters
{
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public string Realm { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
if (authorization == null)
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return;
}
if (authorization.Scheme != "Basic")
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return;
}
if (String.IsNullOrEmpty(authorization.Parameter))
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return;
}
Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);
if (userNameAndPasword == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
return;
}
string userName = userNameAndPasword.Item1;
string password = userNameAndPasword.Item2;
IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
if (principal == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
else
{
// Authentication was attempted and succeeded. Set Principal to the authenticated user.
context.Principal = principal;
}
}
protected abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
CancellationToken cancellationToken);
private static Tuple<string, string> ExtractUserNameAndPassword(string authorizationParameter)
{
byte[] credentialBytes;
try
{
credentialBytes = Convert.FromBase64String(authorizationParameter);
}
catch (FormatException)
{
return null;
}
// The currently approved HTTP 1.1 specification says characters here are ISO-8859-1.
// However, the current draft updated specification for HTTP 1.1 indicates this encoding is infrequently
// used in practice and defines behavior only for ASCII.
Encoding encoding = Encoding.ASCII;
// Make a writable copy of the encoding to enable setting a decoder fallback.
encoding = (Encoding)encoding.Clone();
// Fail on invalid bytes rather than silently replacing and continuing.
encoding.DecoderFallback = DecoderFallback.ExceptionFallback;
string decodedCredentials;
try
{
decodedCredentials = encoding.GetString(credentialBytes);
}
catch (DecoderFallbackException)
{
return null;
}
if (String.IsNullOrEmpty(decodedCredentials))
{
return null;
}
int colonIndex = decodedCredentials.IndexOf(':');
if (colonIndex == -1)
{
return null;
}
string userName = decodedCredentials.Substring(0, colonIndex);
string password = decodedCredentials.Substring(colonIndex + 1);
return new Tuple<string, string>(userName, password);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
Challenge(context);
return Task.FromResult(0);
}
private void Challenge(HttpAuthenticationChallengeContext context)
{
string parameter;
if (String.IsNullOrEmpty(Realm))
{
parameter = null;
}
else
{
// A correct implementation should verify that Realm does not contain a quote character unless properly
// escaped (precededed by a backslash that is not itself escaped).
parameter = "realm=\"" + Realm + "\"";
}
context.ChallengeWith("Basic", parameter);
}
public virtual bool AllowMultiple
{
get { return false; }
}
}
}
If you still want to read more then here is a great article which goes into details. I have copied the above code from this article. It has lots of great information.
If you control or exert significant influence on both sides of the connection, client ssl certificates is a really strong and powerful way of doing this. It's attractive to me in this case because it only requires distributing a trusted CA certificate which can be done before the client certificates are created. It's far more secure than any username and password could ever be ( because the password doesn't need to go across the wire).
Any other solution with authentication I can think of, you're going to have to have some sort of data source to verify the credentials. But x509 solves this problem for you. We've done it at work between applications and other than managing the certificates it works really, really well. And it's basically the most secure thing available.
I don't know much about .net in general, but ( not to lmgtfy ) https://support.microsoft.com/en-us/kb/315588 seems like the step by step format you are looking for.
Just a thought, and it really depends on what you meant by "username/password". If this means "authorization"/access to some API call and you want to ensure that the client is "authorized" to make a call to your API (only apps A, B can make api calls to API - and it seems this is what you're looking for based on your comment above):
As in the comment above, authorization header, using JWT. There is an great/easy JWT library in Nuget
it's pretty much something like a "shared secret" used to sign a "payload" (the JWT)
the "sender" will build the JWT and sign it (and add to header or whatever protocol you want - it can be body if prefer it over headers)
the "receiver" will verify the JWT sent
this includes handling/mitigating "replays" - the JWT spec has an "expire" field (exp) that you can have the library validate as well (or not, it's up to you)
The project site is on Github with samples.
Hth.

How to detect weather the endpoint (KAA SDK) is connected to KAA server or not from application

Is there any mechanism or method or steps to detect the endpoint(KAA SDK) connectivity to the KAA server from the application.
If no, then how can we identifies failure devices through remotely?? or How can we identifies devices that are not able to communicate with the KAA Server after deploying devices in the field??
How one can achieve this requirement to unlock the power of IOT??
If your endpoint will meet some problems connecting to Kaa server a "failover" will happen.
So you must define your own failover strategy and set it for your Kaa client. Every time failover happens strategy's onFialover() method will be called.
Below you can see the code example for the Java SDK.
import org.kaaproject.kaa.client.DesktopKaaPlatformContext;
import org.kaaproject.kaa.client.Kaa;
import org.kaaproject.kaa.client.KaaClient;
import org.kaaproject.kaa.client.SimpleKaaClientStateListener;
import org.kaaproject.kaa.client.channel.failover.FailoverDecision;
import org.kaaproject.kaa.client.channel.failover.FailoverStatus;
import org.kaaproject.kaa.client.channel.failover.strategies.DefaultFailoverStrategy;
import org.kaaproject.kaa.client.exceptions.KaaRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* A demo application that shows how to use the Kaa credentials API.
*/
public class CredentialsDemo {
private static final Logger LOG = LoggerFactory.getLogger(CredentialsDemo.class);
private static KaaClient kaaClient;
public static void main(String[] args) throws InterruptedException, IOException {
LOG.info("Demo application started");
try {
// Create a Kaa client and add a startup listener
kaaClient = Kaa.newClient(new DesktopKaaPlatformContext(), new SimpleKaaClientStateListener() {
#Override
public void onStarted() {
super.onStarted();
LOG.info("Kaa client started");
}
}, true);
kaaClient.setFailoverStrategy(new CustomFailoverStrategy());
kaaClient.start();
// ... Do some work ...
LOG.info("Stopping application.");
kaaClient.stop();
} catch (KaaRuntimeException e) {
LOG.info("Cannot connect to server - no credentials found.");
LOG.info("Stopping application.");
}
}
// Give a possibility to manage device behavior when it loses connection
// or has other problems dealing with Kaa server.
private static class CustomFailoverStrategy extends DefaultFailoverStrategy {
#Override
public FailoverDecision onFailover(FailoverStatus failoverStatus) {
LOG.info("Failover happen. Failover type: " + failoverStatus);
// See enum DefaultFailoverStrategy from package org.kaaproject.kaa.client.channel.failover
// to list all possible values
switch (failoverStatus) {
case CURRENT_BOOTSTRAP_SERVER_NA:
LOG.info("Current Bootstrap server is not available. Trying connect to another one.");
// ... Do some recovery, send notification messages, etc. ...
// Trying to connect to another bootstrap node one-by-one every 5 seconds
return new FailoverDecision(FailoverDecision.FailoverAction.USE_NEXT_BOOTSTRAP, 5L, TimeUnit.SECONDS);
default:
return super.onFailover(failoverStatus);
}
}
}
}
UPDATED (2016/10/28)
From the server side you can check endpoint credentials status as shown in method checkCredentialsStatus() in code below. The status IN_USE shows that endpoint has at least one successful connection attempt.
Unfortunately in current Kaa version there are no ways to directly check if endpoint is connected to server or not. I describe them after code example.
package org.kaaproject.kaa.examples.credentials.kaa;
import org.kaaproject.kaa.common.dto.ApplicationDto;
import org.kaaproject.kaa.common.dto.admin.AuthResultDto;
import org.kaaproject.kaa.common.dto.credentials.CredentialsStatus;
import org.kaaproject.kaa.examples.credentials.utils.IOUtils;
import org.kaaproject.kaa.server.common.admin.AdminClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class KaaAdminManager {
private static final Logger LOG = LoggerFactory.getLogger(KaaAdminManager.class);
private static final int DEFAULT_KAA_PORT = 8080;
private static final String APPLICATION_NAME = "Credentials demo";
public String tenantAdminUsername = "admin";
public String tenantAdminPassword = "admin123";
private AdminClient adminClient;
public KaaAdminManager(String sandboxIp) {
this.adminClient = new AdminClient(sandboxIp, DEFAULT_KAA_PORT);
}
// ...
/**
* Check credentials status for getting information
* #return credential status
*/
public void checkCredentialsStatus() {
LOG.info("Enter endpoint ID:");
// Reads endpoint ID (aka "endpoint key hash") from user input
String endpointId = IOUtils.getUserInput().trim();
LOG.info("Getting credentials status...");
try {
ApplicationDto app = getApplicationByName(APPLICATION_NAME);
String appToken = app.getApplicationToken();
// CredentialsStatus can be: AVAILABLE, IN_USE, REVOKED
// if endpoint is not found on Kaa server, exception will be thrown
CredentialsStatus status = adminClient.getCredentialsStatus(appToken, endpointId);
LOG.info("Credentials for endpoint ID = {} are now in status: {}", endpointId, status.toString());
} catch (Exception e) {
LOG.error("Get credentials status for endpoint ID = {} failed. Error: {}", endpointId, e.getMessage());
}
}
/**
* Get application object by specified application name
*/
private ApplicationDto getApplicationByName(String applicationName) {
checkAuthorizationAndLogin();
try {
List<ApplicationDto> applications = adminClient.getApplications();
for (ApplicationDto application : applications) {
if (application.getName().trim().equals(applicationName)) {
return application;
}
}
} catch (Exception e) {
LOG.error("Exception has occurred: " + e.getMessage());
}
return null;
}
/**
* Checks authorization and log in
*/
private void checkAuthorizationAndLogin() {
if (!checkAuth()) {
adminClient.login(tenantAdminUsername, tenantAdminPassword);
}
}
/**
* Do authorization check
* #return true if user is authorized, false otherwise
*/
private boolean checkAuth() {
AuthResultDto.Result authResult = null;
try {
authResult = adminClient.checkAuth().getAuthResult();
} catch (Exception e) {
LOG.error("Exception has occurred: " + e.getMessage());
}
return authResult == AuthResultDto.Result.OK;
}
}
You can see an more examples of using AdminClient in class KaaAdminManager in Credentials Demo Application from Kaa sample-apps project on GitHub.
Knowing workarounds
Using Kaa Notifications in conjunction with Kaa Data Collection feature. Server sends specific unicast notification to endpoint (using endpoint ID), then endpoint replies sending data with Data Collection feature. Server wait a bit and checks timestamp of the last appender record (typically in database) for your endpoint (by endpoint ID). All messages go asynchronously, so you must select response-wait time according to your real environment.
Using Kaa Data Collection feature only. This method is simpler but has certain performance drawbacks. You can use it if your endpoints must send data to Kaa server by theirs nature (measuring sensors, etc.). Endpoint just sends data to server at regular intervals. When server needs to check if endpoint is "on-line", it query saved data logs (typically database) to get last record by endpoint ID (key hash) and analyze the timestamp field.
* To make effective use of Kaa Data Collection feature, you must add such metadata in settings of selected Log appender (in Kaa Admin UI): "Endpoint key hash" (the same as "Endpoint ID"), "Timestamp". This will automatically add needed fields to every log record received from endpoints.
I'm new to Kaa myself and unsure whether there is a method to determine that directly in the SDK, but a work-around is that you could have an extra endpoint from which you periodically send an event to all the other endpoints and expect a reply. When an endpoint does not reply, you know there's a problem.

Best practice for deploying spring boot application on Amazon

I've devloped a chat bot application using the Facebook Messenger platform.
I used Spring Boot with embedded Tomcat for the web platform.
The application should run on Amazon aws, open to the WWW, and to be used as a webhook for recieving callbacks from Messenger over https.
I need an advice how to secure the application, so it won't be hacked or flooded with requests that are not coming from Facebook.
I thought to make the application require secured (ssl) connection, but using the "security.require_ssl=true" in application.properties didn't do the work. Perhaps I don't know what is the meaning of this and how to configure it propertly.
Is there a best practice how to block requests which are not https requests? Or a way to block requests which are coming outside Messenger in the application level?
Thank you very much!
EDIT
In the meantime, I blocked requests from other IPs in application layer using the handler interceptor:
#Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer, WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (! (request.getRemoteAddr().equals("173.252.88.66") || request.getRemoteAddr().equals("127.0.0.1")|| request.getRemoteAddr().equals("0:0:0:0:0:0:0:1"))){
logger.warn("Request is not coming from authorized remote address: " + request.getRemoteAddr()+". Rejecting");
response.getWriter().write("Unauthorized Address");
response.setStatus(401);
return false;
} else {
return true;
}
}
}
You should check the X-Hub-signature HTTP header available in the requests sent by Facebook to your webhook URL.
In your case, you may define a filter or interceptor for the verification of the signature. You can also do it in your controller as in the this example I found in RealTimeUpdateController.java from the spring social project.
private boolean verifySignature(String payload, String signature) throws Exception {
if (!signature.startsWith("sha1=")) {
return false;
}
String expected = signature.substring(5);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
SecretKeySpec signingKey = new SecretKeySpec(applicationSecret.getBytes(), HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(payload.getBytes());
String actual = new String(Hex.encode(rawHmac));
return expected.equals(actual);
}
a lot to say so I am sure I will miss some points.
setting SSL is a first good thing but make sure you get a certificate. lets encrypt is a good thing if you dont want to pay for SSL certificate.
Just seeing aws provides an alternative to letsencrypt
Security Group You can see Security Group as something similar to a firewall so you can control which port is opened, external and internal flows.
Look at IAM which control who and how can get access to your AWS account
obvious : change your password. do not let default password for installation you could make on the instance
read some of https://aws.amazon.com/security/security-resources/ to get more information about what you can do
it won't be hacked or flooded with requests
sorry to say but most probably it will be - It does not need to be an advanced hacker to run scanner and scan IPs and check open ports / brute force login etc ...
Thanks to Guy Bouallet help I added the signature check.
I added it in my controller and not in the interceptor, to avoid the problem of How to read data twice in spring which seems a little complicated.
So here is it:
#RequestMapping(path = "/")
public void doHandleCallback(#RequestBody String body, #RequestHeader(value = "X-Hub-Signature") String signature) throws IOException {
if (!verifyRequestSignature(body.getBytes(), signature)){
logger.error ("Signature mismatch.");
throw new MismatchSignatureException(signature);
}
MessengerCallback callback = mapper.readValue(body, MessengerCallback.class);
logger.info("Incoming Callback: " + body );
for (EventData entry : callback.getEntry()) {
for (ReceivedMessagingObject message : entry.getMessaging()) {
if (message.isMessage() || message.isPostback()) {
doHandleMessage(message);
}
else if (message.isDelivery()){
doHandleDelivery(message);
}
}
}
}
private boolean verifyRequestSignature(byte[] payload, String signature) {
if (!signature.startsWith("sha1="))
return false;
String expected = signature.substring(5);
System.out.println("Expected signature: " + expected); //for debugging purposes
String hashResult = HmacUtils.hmacSha1Hex(APP_SECRET.getBytes(), payload);
System.out.println("Calculated signature: " + hashResult);
if (hashResult.equals(expected)) {
return true;
} else {
return false;
}
}
And this is the Exception handling class:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Request Signature mismatch")
public class MismatchSignatureException extends RuntimeException {
private String signature;
public MismatchSignatureException(String signature) {
this.signature = signature;
}
#Override
public String getMessage() {
return "Signature mismatch: " + signature;
}

Sending email from AOS server

I am trying to have an email being sent based on some events triggered by users in their client. I do not want the email being sent from the client (as this would require us to allow virtually every workstation in the domain to use the SMTP service) but rather from the AOS server.
I thought of creating a class that would extend RunBaseBatch and use SysMailer in it.
Here's what I have so far.
class Batch_Mailer extends RunBaseBatch
{
str subject;
str body;
str fromName;
str fromAddress;
str toAddress;
str smtpServer;
void new(str _subject, str _body, str _fromName, str _fromAddress, str _toAddress)
{
subject = _subject;
body = _body;
fromName = _fromName;
fromAddress = _fromAddress;
toAddress = _toAddress;
smtpServer = 'mail.domain.ca';
super();
}
public boolean canGoBatchJournal()
{
return true;
}
public void run()
{
SysMailer mail;
;
super();
try
{
mail = new SysMailer();
mail.fromAddress(fromAddress, fromName);
mail.SMTPRelayServer(smtpServer);
mail.tos().appendAddress(toAddress);
mail.htmlBody(strfmt(body));
mail.subject(subject);
mail.sendMail();
}
catch
{
//Log something maybe, but nice if the infolog would not pop up...
}
}
}
Here's how I use it:
Batch_Mailer mail;
mail = new Batch_Mailer("Subject.", strfmt("#VDX488", vendTable.AccountNum, curUserId()), "AX Alerts",
"AXAlerts#domain.ca", "test.mailbox#domain.ca"
Unfortunately, this seems to run in the client. If I run the code on the dev box VM that has the AOS server on (which can use the SMTP service) the email fires, but not if I run it in the client on my physical box (which is not allowed to use the SMTP service).
I thought extending RunBaseBatch and overriding run would do it, but apparently not. Any ideas ?
I'm also wondering if this approach will fail since I don't think most users can run batches using their accounts... Perhaps I'll have to use impersonation?
Thanks!
Extending RunBaseBatch doesn't imply that it is always executed on the server tier - where the code is actually executed depends on where the object lives.
Therefore you could ensure that the code is always executed on the server tier by ensuring that such objects are always created there. To accomplish this just create a server static method which you use to create new instances of your class.
Example:
public static server Batch_Mailer newOnServer(
str _subject,
str _body,
str _fromName,
str _fromAddress,
str _toAddress)
{
;
return new Batch_Mailer(_subject, _body, _fromName, _fromAddress, _toAddress);
}
After that you just have to call this static method instead of directly using new:
mail = Batch_Mailer::newOnServer("Subject.", strfmt("#VDX488" ...
mail.run();
DAXaholic's answer is spot on answer to your question, but perhaps you should consider using the built-in AX framework for sending emails instead of coding up your own method. I'd imagine you will have less problems down the road and an easier upgrade path to 2012+.
See my blog post:
http://alexondax.blogspot.com/2013/09/how-to-properly-send-emails-with-built.html

Resources