Protecting click once web deployed installations - deployment

I have a link on my website to the standard publish page generated by Visual Studio. My concern is that if anybody finds out the URL to that page, they can download my software. Sure, I could password protect the page with the link, but it still would not be protecting the download URL. Are there any ways to secure the click once upload? I have looked around, and it seems like I am stuck in this sense.

Public URL is a security issue in ClickOnce Deployment. However, there is a solution for your problem if your web server has windows and .NET installed. Tell me if you have one ? I will have to come up with another workaround for Linux web server in case you have that.
Brief
Firstly, a bit of information about ClickOnce deployment. When you deploy the application, the GET requests on the server made are (assuming WebDir is the publish directory on the server)
G-1. GET /WebDir/setup.exe (Initial download)
G-2. GET /WebDir/MyApp.Application (setup.exe -url request)
G-3. GET /WebDir/MyApp.Application (.application deployment provider URL request)
G-4. GET /WebDir/Application Files/MyApp_1_0_0_0/MyApp.exe.manifest (Application manifest request)
G-5. GET /WebDir/Application Files/MyApp_1_0_0_0/MyApp.exe.deployand other .deploy files ... (Application file requests)
Implementation
Now, the solution is to intercept these file requests on the server. On IIS, you can attach a custom HTTPHandler and handle the request. On Apache, you can redirect requests to a PHP code using .htaccess files. Apart from this, you will have to generate unique identifier uid for client instances downloaded from the server (can be your license key) and put that in the deployment provider URL query parameters.
Directory Structure
Create an "Application" folder inside your WebDir and restrict access to /WebDir/Application/. Rest everything can be there inside /WebDir/
File Requests
So here's what you do on a Apache web server hosted on a windows machine:
Create a custom download page or use the one created from publishing the application using Visual Studio (but you will have to edit it manually!). Let's assume that page is /WebDir/Download.php
After authenticating user from Download.php, you have to send setup.exe from your code (can do it with readfile() in PHP) to the user. However, the catch is bootstrapper (setup.exe) after installing will do a GET request [G-2]. Don't forget now, that you have to validate this file request. So basically you change the "setup.exe -url" property to include uid before returning the file. For eg: change it to /WebDir/uid/MyApp.Application [G-2]. You can use MsiStuff.exe to change the URL property for the bootstrapper.
Using a .htaccess file, rewrite [G-2] to /WebDir/Handler.php?user=uid. From Handler.php, you can check if it is a valid uid. If it is valid, you will have to include the uid in the deployment provider URL and "Dependent Assemblies Path" in deployment manifest so that if an upgrade request comes (It essentially requests the deployment manifest), you can validate the user there too. Add uid to query string parameters. For eg: change it to /WebDir/MyApp.application?user=uid [G-3]. Don't forget that you will have to resign the manifests once you modify them. Use Mage or write your own code to do that.
So finally, the GET requests on the server will be (assuming uid=1f3rd)
G-1. GET /WebDir/Download.phpAction: return setup.exe with the -url changed
G-2. GET /WebDir/Application/setup.exe/1f3rd/MyApp.ApplicationAction: redirect, validate user, change URL, re-sign and return file
G-3. GET /WebDir/Application/setup.exe/MyApp.Application?user=1f3rdAction: redirect, validate user and return file
G-4. GET /WebDir/Application/1f3rd/Application Files/MyApp_1_0_0_0/MyApp.exe.manifestAction: redirect, validate user and return file
G-5. GET /WebDir/Application/1f3rd/Application Files/MyApp_1_0_0_0/MyApp.exe.deployand other .deploy files ...Action: redirect, validate user and return file
Pros
Application is successfully deployed and upgraded only if all the requests have a valid uid in the URL present.
You can now identify different instances of application on client systems. You can track the update history, do a selective version upgrade/downgrade and much more !
Cons
You will need a windows server to implement the above since you need mage.exe | your-own-.NET-code-signing-application and Msistuff.exe.
You may have minor performance issues since you are performing validation on every file request. You can choose to skip validation on .manifest and .deploy file requests.
You will have to ensure proper security for companies certificate which will be present on the web server for signing (You can store it on the server local file-system if you have the full server to yourself. In that case, it is fine unless somebody breaks into machine itself !)
If you want me to make something clear or explain in detail, feel free to ask. In case you have suggestions for modification to the above, post that too.
I will write a detailed CodeProject article if I have spare time someday.

Related

Setting up load-balancer based on authenticated users

I'm trying to set up a loadbalancer that would redirect to specific version of an application certein users. So far i was using Blue/Green deployment strategy (so once i made new version of an app i created new environment and redirected traffic there). Now i would like to change this approach. I want to be able to specify users (more experienced or whatever) that would see new site after authentication while the others would still be redirected to old one. If something goes wrong with new version all users will see old version. Currently my loadbalancing is made in apache and authentication is done on application level. So is this even possible? I know i could hardcode it in application but what if there is a bug in new feature and new users are still being redirected there? I would then need to stop application for all users and rollback to old version and that's bad i guess. I was thinking about using external CAS however didnt find any information if it would be possible then. So i would like to ask is it possible and are there any tools (maybe some apache plugin) for that purpose?
Here's a working solution with nginx
create conf.d/balancer.conf
put the code into it (see below)
docker run -p8080:8080 -v ~/your_path/conf.d:/etc/nginx/conf.d openresty/openresty:alpine
use curl to play with it
balancer.conf:
map $cookie_is_special_user $upstream {
default http://example.com;
~^1$ http://scooterlabs.com/echo;
}
server {
listen 8080;
resolver 8.8.8.8;
location / {
proxy_pass $upstream;
}
}
testing
curl --cookie "is_special_user=1" http://localhost:8080
It would return the contents of scooterlabs.com dumping the request it receives
curl http://localhost:8080
Produces the contents of example.com
explanation
the idea is that you set a special cookie to the users you treat as special by the backend app after they get authorized as usual
of course it would only work if both app versions are served on the same domain so that the cookie is seen by both versions
after that you balance them to a desired server depending on the cookie value
you can easily disable such routing by tweaking your nginx config file
with this approach you can come up with even more complex scenarios like setting random cookie values in the range 1-10 and then gradually switching some of the special users in your config file i.e. start with those having value 1, after that 1-2 etc

WSO2 Carbon 404 Error Redirection for Webapp Deployment?

We are using WSO2 Carbon 4.2.0 through the WSO2 Application Server (AS) package. In replacing an older, highly customized Carbon installation (provided by a company that no longer supports the product, has abandoned it and refuses to work on it, and left us no details on how/what they modified in Carbon), we have deployed a couple web applications in the webapps container as they were deployed before in the older instance. We have changed our WebContextRoot in the carbon.xml from the default "/" to a sub-URL of ex: "/stuff", as is also detailed in the self-answered SO question here. However the answer given there is not detailed in what the OP actually encountered when he modified his WSO2 instance.
In testing the above configuration we noticed that if a user were to go to a non-existent web address on the server, depending on the format of the URL they are either:
redirected to a blank page;
receive a "500 Internal server error" (I suspect this is the embedded Tomcat?);
get sent to the Carbon login page (which we definitely do not want to happen for security reasons); or
get an XML document stating:
<faultString> The service cannot be found for the endpoint reference (EPR) /stuff/services/nonexistantservicename </faultString>
At least in the case of missing content we wish the user to be sent to a standardized 404 error page, or at the least be sent an HTTP 404 error by the server. For services the XML error is palatable, we can deal with that.
The only option for us right now to circumvent this issue is to place a proxy in front of the WSO2 instance, which would be another layer to manage and tune, and possibly degrade performance. Please know that I am not a programmer but just an admin with DevOps experience. I would not know how to handle this with e.g. a Java solution or re-coding parts of WSO2. Customizing the core product would also hamper future upgrades of WSO2, a scenario we are trying to dig ourselves out of now as detailed above. Is there no internal WSO2 mechanism to handle non-existent content? Can we not redirect any errors to a standard canned response page?

SOAP/HTTP using different folder mappings in ColdFusion

I'm experiencing some strange behaviour with a ColdFusion 11 server, which (among other things) publishes some web services accessed via both SOAP and HTTP. The server itself is Windows 2012, running IIS. Actual folder config is as follows:
IIS has two websites configured, 'BOB' and 'BOB_Services'. Both have been configured with the CF Server Config tool so that CF handles .cfc, .cfm files. They share a common CFIDE config.
BOB's root is I:/inetpub/BOB
BOB_Services's root is I:/inetpub/BOB_Services
There is a folder mapping configured in CF Admin from '/' to 'I:/inetpub/BOB'. Don't ask me why, no one seems to know.
Normally there is a services.cfc file in BOB_Services ONLY. Yesterday we accidentally copied that same file into the BOB root folder, and all of our SOAP services using BOB_Services\services.cfc started throwing errors. Yet I can query the same webservice via HTTP (eg. using http://bob/services.cfc?method=function1&param1=0 ....etc) and get a valid result.
This is a reference answer in case anyone else comes across this strange behaviour.
It appears that when BOB_Services/services.cfc is called using HTTP GET, the folder mapping
'/' -> 'I:/inetpub/BOB'
is ignored and the actual file used to process the request is I:/inetpub/BOB_Services/services.cfc.
When a function in BOB_Services/services.cfc is called using a SOAP client, the folder mapping is invoked and the file used to process the request is I:/inetpub/BOB/services.cfc, IF IT EXISTS. If it does not exist, the file I:/inetpub/BOB_Services/services.cfc is used as expected.
This behaviour appears to be entirely repeatable - I can make a SOAP request, get one result, change the mapping, make another request and get the other result.

Finding latest TeamCity Backup via REST API

I found plenty of information and example about triggering TeamCity 8.1.2 backups via the REST API.
But leaving the backup files on the same server is pretty useless for disaster recovery.
So I'm looking for a way to copy over the generated backup file to another location.
My question is about finding the name of the latest available backup file via the REST API -
The Web GUI includes this information under "Last Backup Report" under the "Backup" page of the Server Administration.
I've dug through https://confluence.jetbrains.com/display/TCD8/REST+API#RESTAPI-DataBackup and the /httpAuth/app/rest/application.wadl on my server. I didn't find any mention of a way to get this info through the REST API.
I also managed to trigger a backup with a hope that perhaps the response gives this information, but it's not there - the response body is empty and the headers don't include this info.
Right now I intend to fetch the HTML page and extract this information from there, but this feels very hackish and fragile (the structure of the web page could change any time).
Is there a recommended way to get this information automatically?
Thanks.
JetBrains support got back to me with the right answer - I should use a POST method, not GET, even if the request body is empty.
Here is an example of a working request:
curl -u user:password --request POST http://localhost:8111/httpAuth/app/rest/server/backup?includeConfigs=true'&'includeDatabase=true'&'fileName=testBackup
And the response to that contains a plain file name in text: testBackup_20150108_141924.zip

Perl SOAP::WSDL accessing HTTPS Unathorized error

I'm trying to generate a Perl library to connect to a WebService. This webservice is in an HTTPS server and my user has access to it.
I've executed wsdl2perl.pl several times, with different options, and it always fails with the message: Unauthorized at /usr/lib/perl5/site_perl/5.8.8/SOAP/WSDL/Expat/Base.pm line 73.
The thing is, when I don't give my user/pass as arguments, it doesn't even asks for them.
I've read [SOAP::WSDL::Manual::Cookbook] (http://search.cpan.org/~mkutter/SOAP-WSDL-2.00.10/lib/SOAP/WSDL/Manual/Cookbook.pod) and done what it says about HTTPS: Crypt::SSLeay is instaleld, and both SOAP::WSDL::Transport::HTTP and SOAP::Transport::HTTP are modified.
Can you give any hint about what may be going wrong?
Can you freely access the WSDL file from your web browser?
Can someone else in your network access it without any problems?
Maybe the web server hosting the WSDL file requires Basic or some other kind of Authentication...
If not necessary ,I don't recommend you to use perl as a web service client .As you know ,perl is a open-source language,although it do support soap protocol,but its support do not seem very standard.At first,its document is not very clear.And also ,its support sometimes is limited.At last,bug always exists here and there.
So ,if you have to use wsdl2perl,you can use komodo to step into the code to find out what happened.This is just what I used to do when using perl as a web service client.You know ,in the back of https is SSL,so ,if your SSL is based on certificate-authorized,you have to set up your cert path and the list of trusted server cert.You'd better use linux-based firefox to have a test.As I know ,you can set up firefox's cert path and firefox's trusted cert list.If firefox can communicated with your web service server succefully,then,it's time to debug your perl client.
To debug situations with Perl and SOAP, interpose a web proxy so you can see exactly what data is being passed and what response comes back from the server. You were getting a 401 Not authorized, I expect, but there may be more detail in the server response.
Both Fiddler http://docs.telerik.com/fiddler and Charles proxy https://www.charlesproxy.com/ can do this.
The error message you quote seems to be from this line :
die $response->message() if $response->code() ne '200';
and in HTTP world, Unauthorized is clearly error code 401, which means your website asks for a username and password (most probably, some website may "hijack" this error code to cater for other conditions like a filter on the source IP).
Do you have them?
If so, you can
after wdsl2perl has run, find in the created files where set_proxy() is called and change the URL in there to include the username and password like that : ...->set_proxy('http://USERNAME:PASSWORD#www.example.com/...')
or your in code, after instantiating the SOAP::WSDL object, call service(SERVICENAME) on it (for each service you have defined in your WSDL file), which gives you a new object, on which you call transport() to access the underlying transport object on which you can call proxy() with the URL as formatted above (yes it is proxy() here and set_proxy() above); or you call credentials() instead of proxy() and you pass 4 strings:
'HOSTNAME:PORT'
the realm, as given by the webserver but I think you can put anything
the username
the password