.net Best Practices Cloud Foundry Demos How-tos pas pas for windows Pivotal Cloud Foundry products steeltoe Tanzu Application Service

Run .NET Framework Apps on a Modern Platform with an SMB Network Share? Steeltoe Makes It Easy.

Here’s a common scenario: using a network shared folder within an application. I see this in legacy .NET applications. There are two share types, NFS and SMB. NFS is more common in the Linux world; SMB rules the roost on the Windows side.

Pivotal Application Service customers use volume services to offer shares to a modern microservice. But it’s more challenging when you want to move legacy .NET apps to a cloud platform.

Ideally, you’d “bridge” the gap here with some minor re-design of the application, and native support from PAS. In a perfect world, it’s as simple as moving values that once lived in web.config to environment variables. Here, very little code has to change.

Network shares are less common in modern .NET. There are more cloud-native ways to approach this scenario. Message queues, caches, S3-compatible stores, and NoSQL stores are perfectly good options. These services offer resiliency, decoupled backing services, and statelessness. But that’s little consolation for our legacy scenario.

Thankfully, there is something that can offer comfort and aid: PAS for Windows (PASW) and Steeltoe!

Both services are purpose-built for bringing a .NET application from IIS to the cloud world. PASW is how you can bring .NET Framework apps to a modern app platform. Steeltoe is a popular tool for building .NET microservices. (1.6 million downloads and counting!)

In this post, we’ll demonstrate how easy it is to implement net use within a cloud-native .NET microservice.

We can start with a level-set on configuration and prerequisites. There are many ways to offer network shares. The ideas below can simply be used for comparison.

Configurations

Our environment includes the PASW 2.3 tile (with an 1803 Windows Stemcell), Credhub runtime, and the PCF 2.3 Credhub service broker. We’ll use Visual Studio 2017 as the IDE, targeting either .NET Core 2.1 or .NET Framework 4.6 running on Windows.

Prerequisites & Assumptions

Windows SMB protocol runs on specific network ports. Ensure these ports are open, so your app can communicate with the server hosting the share, elsewhere on the network.

View the code

You can find the Visual Studio solution at https://github.com/pivotal/dotnet-smb-network-shares. It includes PowerShell scripts to create the tested environment.

Learn about the Steeltoe Framework

View samples, read the docs, and see the source code at https://steeltoe.io

Create the SMB Share

You’ll need an address for the share, plus credentials. Let’s run a Power Shell script to set things up. This was tested on Windows 2016 server, but is compatible with most modern Windows Server versions. (Note: this script must be run on the server as administrator, or with a highly privileged account.)

The script will create a new local user named shareWriteUser, and then it will add that user to the local ‘Users’ group. From there, it will create a folder located at c:test_network_share (letting the default users and groups have access). Finally, it will share the folder, and give the new user write permission and read-access to the Everyone & Guest groups.

$ErrorActionPreference = "Stop"

$writeUser = "shareWriteUser"
$folderPath = "c:test_network_share"
$shareName = "test_network_share"
$password = ConvertTo-SecureString "thisIs1Pass!" -AsPlainText -Force

#Create local user accounts
New-LocalUser $writeUser `
 -Password $password `
 -FullName "NetworkFull Write" `
 -Description "A write account to test network shared"

echo "-----> Created user account"

#Add accounts to users group
Add-LocalGroupMember -Group "Users" -Member $writeUser
echo "-----> Added to group"

#Create folders to share
New-Item -ItemType directory -Path $folderPath
echo "-----> Created folder"

#Share the folders, Users group gets read access
New-SmbShare -Name $shareName `
 -Path $folderPath `
 -ReadAccess "Everyone", "Guests" `
 -FullAccess $writeUser

The username and password of the local account with write permission is shareWriteUser:thisIs1Pass!The network address of the new share is \<server IP>test_network_share. One other quick note: PASW 1709 and 1803 stemcells require the IP address of the sharing server. They cannot resolve the machine’s name.

Create the Service in PAS

It’s not a good idea to hard code things like network addresses and account credentials. For the network address, you probably have separate development and production addresses. We want to build the app once and promote it through different environments without fiddling with config. Our app should hold a reference to the network address, but not the actual value. We can accomplish this with User Provided Services. This way, we bind the service to the app in the manifest. Then we use Steeltoe to parse the value and make it available within our app as a variable.

Because we use PAS, we have an added best practice to improve our security posture. We never want a password to be transmitted in clear text, or to be easily readable. We use the Credhub Service Broker to mitigate this risk. If your organization has a separation of duty between developers consuming a network share and the administrator that has access to the account creds, this will be a great fit!

Let’s create a powershell script that makes 2 services available in a given Org/Space. First, it will create a Credhub service instance, using the account values we just created in the previous step. Then, it will create a User Provided Service instance, that holds the network share address. If desired, you could easily distribute the two cf commands to different teams who have access to each piece of info.

Protip: This script assumes the user running it has already logged in and targeted the correct Org/Space. You’ll need a minimum role of SpaceManager to run this.

$ErrorActionPreference = "Stop"

#assume you have already logged in and targeted the correct Org/Space

$serviceName = "credhub"
$servicePlan = "default"
$serviceInstanceName = "test-network-share"

$writeUser = "shareWriteUser"
$password = "thisIs1Pass!"

$shareNetworkAddress = "\\<SERVER_IP>\test_network_share"

$serviceTags = [string]::Format('{0},test-smb-share',$serviceInstanceName) #comma delimited

$credsParamJSON = [string]::Format('{{"share-username":"{0}","share-password":"{1}"}}',$writeUser,$password)
$addressParamJSON = [string]::Format('{{"share-network-address":"{0}"}}',$shareNetworkAddress)

#Create the service instance
cf create-service $serviceName $servicePlan $serviceInstanceName -c $credsParamJSON -t $serviceTags

#Create a user provided service with the network address of the SMB share
cf create-user-provided-service "network-address" -p $addressParamJSON  -t $serviceTags

 
Now created the network share address and credentials for connecting; in a modern, cloud-native way. The app can be moved from Space to Space on the platform (or to a different PAS foundation all together) and never be re-compiled. This way, your unit tests and integration tests remain valid!
 
Note the use of <SERVER_IP> in the above script. Replace this with the IP address of the server hosting the share. At this time machine names or FQDN are not supported.

Consuming the Bound Services with Steeltoe

It’s time to retrieve the values that were made available in the bindable services. First, we need to tell PAS to bind the services to our application upon deployment. This is accomplished in the manifest.yml file.

services:
 - test-network-share
 -
network-address

Now we have bound (or connected) the application to the services. Every time BOSH creates a new instance of our application, it will take care of the binding for us.

Next, we retrieve the values from the bound services. Steeltoe provides a very easy way of doing this through the Steeltoe.Extensions.Configuration.CloudFoundry package. (For a complete example refer to the public repo.)

Let’s retrieve values from Credhub broker:

string userName = _serviceOptions.Services["credhub"]
       .First(q => q.Name.Equals("test-network-share"))
       .Credentials["share-username"].Value;
string password = _serviceOptions.Services["credhub"]
       .First(q => q.Name.Equals("test-network-share"))
       .Credentials["share-password"].Value;

 

Now, we get the values from the User Provided Service:
 
_shareFolderAddress = _serviceOptions.Services["user-provided"]
       .First(q => q.Name.Equals("network-address"))
       .Credentials["share-network-address"].Value;
 
Notice we used the values established from running the above cf script. We had to tell the Steeltoe CloudFoundry package to choose the correct service and the correct value saved within that service. This way, the app can have many other services bound to it without conflict.

Now that we have all needed values to put our network share to use.

The script below creates a System.Net.NetworkCredentials object with needed values. Then it uses a Steeltoe.Common.Net.WindowsNetworkFileShare object to establish the net use connection. From here we can write files to the share!

string destFilePath = _shareFolderAddress + @"the-pivotal-story-copy.pdf";
string sourceFilePath = @".the-pivotal-story.pdf";

NetworkCredential shareCredential = new NetworkCredential(userName, password);

//Connect using net use command
using (WindowsNetworkFileShare networkPath = new WindowsNetworkFileShare(_shareFolderAddress, shareCredential)) {
   //write file
   System.IO.File.Copy(sourceFilePath, destFilePath);   
                                 

 
In the initial environment set up, we gave the Users & Guests account groups read access to the network share.  Below is an example of a RESTful endpoint listing the share’s contents with no credentials.
 
public ActionResult<IEnumerable<string>> ListFilesFromShareNoCreds()
{
   string[] fileEntries = null;
   fileEntries = Directory.GetFiles(_shareFolderAddress);
   return fileEntries;
}

Note the Steeltoe.Common.Net package can only be used by an app running on Windows Server, not Linux. This is due to its use of the mpr.dll library. For a deeper look, check out the Steeltoe source here. For an explanation of mpr, check out this post.

Sign-Up for a Free Trial of PAS, and Start Using Steeltoe

To get more familiar with Steeltoe, head over to Pivotal Web Services(PWS) and create a free account. PWS is a hosted Cloud Foundry run by the experts at Pivotal. It’s a great place to get started with modern software development and cloud-native .NET. And thanks to all the sample apps created by the Steeltoe team, you can easily run starter scripts and see everything in action.

Using PCF? Then you can replay our tutorial in your own environment. When testing a network share on your foundation, take note of the pre-req’s and assumptions to make sure everything is configured properly. As a first step, simply push the demo app, and then working from there.

References

Steeltoe Framework

Windows Server mpr process

A note about Stemcells

Our opinions are usually a strong reason why so many prefer using PCF. As you explore the list of services Microsoft recommends turning to manual or disabling, you might find that our configuration of PASW stemcells doesn’t always match up. That's because we have the luxury of knowing exactly how the operating is going to be used and can lock down things further than their recommendations. Also don’t forget about the wonderful things BOSH does for us. There are a few cases where BOSH provides a similar service you might find in the base 1709 or 1803 operating system.

In the spirit of software-defined things, you can see exactly how we prep a PASW stemcell by reading the scripts. Compare the list of services in the script to Microsoft’s recommendation and you’ll have a [short] diff. Alternatively, if your PASW stemcells have been deployed with the ssh feature, you could run cf ssh on a deployed app and then powershell "Get-Service | Format-Table -Property Name,StartType" to see a definitive list of the state of every service.

If you would like to see more about Microsoft’s point of view on the matter, they’ve posted an opinion in the Security Guidance area about what services should be disabled versus manual start. For consuming network shares we are specifically interested in NetBIOS service. This provides support for network name resolution. In our case (as you saw in the step-by-step and the Github repo) we’re going to need the IP address of the VM hosting the network share, and (when applicable) the creds to connect.