Category: Security

Handling MongoDB Secrets At Build Time with Docker

Secure Database Image

CI/CD is an important part of our team’s process. We regularly build small back-end web services to support the apps we build. We often use a combination of Node.js and MongoDB as a stack for services, and deploy them using Docker. As we keep ramping up our secure practices, we have been removing secrets embedded in projects using CredStash to store and serve our secrets. Since our servers are hosted at AWS, this is a good solution for us. CredStash is another topic entirely, but my team has it set up on their workstations and the build servers are configured to use it as well. The solution presented here uses CredStash, but it could be any other secret storage system as long as you can access the system via a shell script. The important part is that the secrets are acquired at build time and not persisted anywhere on the build server or in the resulting containers.

We want to be able to setup and populate the database at creation, and also to reset the database to its initial state during the development process. The best case scenario is to simply do this during the build process, so no one has to log into the server to do any setup or cleanup. Just kick off a build, which anyone on the team can do.

We want to run MongoDB in authenticated mode because it’s a problem otherwise. Security is a focus for us. There are four things we need to accomplish with the MongoDB database:

  1. Create an Administrative user
  2. Create a service account for the web service app
  3. Populate the collections with initial state
  4. Allow the database to be set back to initial state

Our build system is based on Jenkins, and we are using Pipelines so our build job configuration is persisted in source control. Our build script defines multiple build types, for the purposes of this article we’ll focus on two of them, Development and Reset, where Development is the default and happens on every git push, and Reset is used to set the database back to this initial state. I’m not going to cover all aspects of the build job, but we are using Docker Compose to create a container for a Node app and a container for MongoDB.

Let’s start with a script to populate the database. We’ll create a JavaScript file with all the base data being inserted something like this, but with many more lines and collections:

db.request.remove({});

db.request.insertOne({
	"caseNumber": "1234567890",
        "zipCode": "48439",
        "requestLeniency": false,
        "firstName": "Andy",
        "lastName": "Smith",
        "issueDate": "04/01/2018"
        });
        
db.request.insertOne({
	"caseNumber": "987654320",
        "zipCode": "48185",
        "requestLeniency": true,
        "firstName": "Emily",
        "lastName": "Juarez",
        "issueDate": "04/07/2018"
        });

The first line removes any existing documents for the collection so we can use this file to reset the collection later. On first run this line has no effect. This file gets saved as setup.js in the project repository. The Dockerfile for the MongoDB container adds this file into the image so the file is part of the container:

FROM mongo:3.4

ADD setup.js /data/setup.js

In our docker-compose.yml we are mapping the /data/db of the MongoDB container to the file system of the Docker host in order to persist the database data across builds. You could achieve something similar with a data container. In this example the database is called sample and the MongoDB container is called test-db.

The next step is to persist the secrets in CredStash. I create strong passwords then persist them to CredStash using my terminal:

credstash put db-admin StrongPassword
credstash put db-user AnotherStrongPassword

Now create a shell script that creates users and populates the database. First set up the accounts and get their passwords from Credstash:

ADMIN_USER="admin"
ADMIN_PASS="$(credstash get db-admin)"
APP_USER="service"
APP_PASS="$(credstash get db-user)"

Next, assuming it’s a first-time build of the container, create an admin user and a service account:

docker exec test-db mongo admin --eval "db.createUser({user: '${ADMIN_USER}', pwd: '${ADMIN_PASS}', roles:[{role:'root',db:'admin'}]});"

docker exec test-db mongo sample -u $ADMIN_USER -p $ADMIN_PASS --authenticationDatabase admin --eval "db.createUser({user: '${APP_USER}', pwd: '${APP_PASS}', roles:[{role:'dbOwner', db:'sample'}]});"

After creating the users the script creates a file we can look for on later builds in order to skip creating the users again because that won’t work on subsequent builds. Also, the script restarts the application container, because it won’t be able to connect initially since the container is up and running before the database users are set up.

docker exec test-db touch /data/db/.mongodb_password_set

docker restart test-service

The last step is to populate the database with initial state. The JavaScript file is in the container already.

docker exec test-db mongo sample -u $APP_USER -p $APP_PASS --authenticationDatabase courthack /data/setup.js

Here is the entire script (data.sh) including all the checks for different states:

#!/usr/bin/env bash

ADMIN_USER="admin"
ADMIN_PASS="$(credstash get db-admin)"
APP_USER="service"
APP_PASS="$(credstash get db-user)"

FOUND=0
docker exec test-db ls /data/db/.mongodb_password_set || FOUND=$?

echo ""
echo "Previous DB setup file found: ${FOUND}"
echo ""

if [ "$FOUND" -ne "0" ]; then

	echo ""
	echo 'DATABASE:  *** Creating Admin User ***'
	echo ""
	sleep 1

	docker exec test-db mongo admin --eval "db.createUser({user: '${ADMIN_USER}', pwd: '${ADMIN_PASS}', roles:[{role:'root',db:'admin'}]});"

	echo ""
	echo 'DATABASE:  *** Creating App User ***'
	echo ""
		
	sleep 2

	docker exec test-db mongo sample -u $ADMIN_USER -p $ADMIN_PASS --authenticationDatabase admin --eval "db.createUser({user: '${APP_USER}', pwd: '${APP_PASS}', roles:[{role:'dbOwner', db:'sample'}]});"

	docker exec test-db touch /data/db/.mongodb_password_set

	sleep 1
	
	docker restart bench-service
fi

if [ "$FOUND" -ne "0" -o "$BUILD_TYPE" = "Reset" ]; then
	echo ""
	echo 'DATABASE:  *** Loading or Restoring Initial Data ***'
	echo ""
	
	docker exec test-db mongo sample -u $APP_USER -p $APP_PASS --authenticationDatabase courthack /data/setup.js
fi

echo ""
echo "DATABASE:  *** Database Setup Complete ***"
echo ""

In the Jenkinsfile.groovy configuration of the pipeline, one of the steps can simply be to execute the above shell script:

// Lots of stuff omitted for brevity....

static final String BUILD_TYPE_DEV = 'Development'
static final String BUILD_TYPE_RESET = 'Reset'

properties(
    [
        parameters([choice(choices: [BUILD_TYPE_DEV, BUILD_TYPE_RESET], description: 'Reset restores the database to starting state', name: 'BUILD_TYPE')]), 
    ])

    
switch (params.BUILD_TYPE) {
	case BUILD_TYPE_DEV:
		// Do all the dev build stuff (omitted for brevity)
		break
	case BUILD_TYPE_RESET:
	   stage('Data Reset') {
	   		setExecutePermissions()
	   		sh './data.sh'
	   }
		break
	default:
		echo "Unsupported build type!"
		currentBuild.result = 'FAILURE'
		break
}

void setExecutePermissions() {
    echo "Setting Execute Permissons"
    script {
        sh 'chmod +x data.sh'
    }
}

So now the MongoDB database has two users set up and the passwords are not in either the build scripts or in an environment variable. They only existed in memory during build time. And the database ie pre-populated for development, and can be reset any time.

Github repo with sample project

Android Certificate Pinning

Barnum.aktie

Securing your web sites and services using HTTPS is something you should be doing no matter what. Last year the government mandated all their sites move to HTTPS, and even Google is rewarding secure sites in its ranking algorithm. There is no reason for not using HTTPS any more. Since HTTPS is the baseline for web apps, certificate pinning should be the baseline for mobile apps interacting with the web.

OWASP published a good description of certificate pinning. To summarize, pinning a certificate means that your app is verifying that the site the app is communicating with is the actual site by comparing the certificate presented by the site to one bundled in the app. This prevents a man-in-the-middle attack on your app.

Why is this important to your app? It matters greatly if you also own the web service API or use a proprietary or paid API. Attackers can use a man-in-the-middle attack to reverse-engineer the web service interface, or to inject malicious data into the payload sent by your app to the web service. In fact, I have used this technique in the past to deconstruct a vendor’s API to better understand how to call it. Lucky for me (but bad for consumers) that they didn’t pin certificates in their Android app in the Play Store.

How Do I Pin a Certificate?

Get the Certificate

First, you must acquire the certificate. Luckily you only need the certificate, and not the private key. If your team/company built the web services, you can get the certificate from them. If you are consuming a public API, there are a variety of ways to get the certificate. I just use OpenSSL from the command line:

ex +'g/BEGIN CERTIFICATE/,/END CERTIFICATE/p' <(echo | openssl s_client -showcerts -connect your.company.com:443) -scq > export.crt

In all likelihood, there will be more than one certificate found by that command, known as the certificate chain. You need the whole chain, and the above command will place those into the file. This file will be placed into the Java keystore.

Create the Keystore

You will need to download the BouncyCastle jar which is a cryptography API we’ll use to convert the certificate into the required .bks keystore. Create the keystore from the command line:

keytool -importcert -v -trustcacerts -file export.crt -alias ca -keystore pin.bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/bcprov-jdk15on-155.jar -storetype BKS -storepass thekeystorepassword

Move the resulting .bks file into the res/raw folder of your Android Studio project.

Using the Keystore in Code

To keep the example simpler, we’ll look at how to use it directly with the HttpsURLConnection object. We’ll open the keystore, set its contents to an SSLContext, and then add a TrustManagerFactory to the SSLContext. Finally, we’ll associate the SSLContext object to the HttpsURLConnection and then the code can proceed as normal from there.


KeyStore trusted = KeyStore.getInstance("BKS");
InputStream store = app.getResources().openRawResource(R.raw.pin);
trusted.load(store, "thekeystorepassword");
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trusted);
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

final URL networkUrl = new URL("https://your.url.com/resource");
final HttpsURLConnection conn = (HttpsURLConnection) networkUrl.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
conn.setRequestMethod("GET");

final InputStream inputFromServer = conn.getInputStream();

But I Use Retrofit!

(updated 4-19-2017)

Retrofit and the OkHttpClient make it even easier. You don’t need the certificate in a keystore. All you need is a hash of the public key, which you can get using this command:

openssl s_client -connect www.yourdomain.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

In your Android code you use the CertificatePinner object and inject that into the OkHTTPClient builder before building your Retrofit object. Note the prepending of the hash type and a slash before the actual hash value:


CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add("www.yourdomain.com", "sha256/UXRFPJGwFvvyJI3vFOMIc19r0JNlNSQydEnYRrZI/W4=")
    .build();
client = httpClient.certificatePinner(certificatePinner).build();

builder = new Retrofit.Builder()
    .baseUrl(Settings.BASE_URL)
    .client(client)
    .addConverterFactory(GsonConverterFactory.create());

Security

So some of you may be thinking:

Hey, hold on there. The certificate (and keystore password) is in my app now. Can’t someone decompile the app and get the certificate?

Yep, that’s true, someone could get the certificate (unless you used Retrofit), which they can technically fetch even without your app. The good news is that they can’t use the certificate to fake being the web server without the private key, which we never used here or included in the keystore. The certificate is publicly available, you aren’t decreasing the security of your app, you are increasing it!

Also, I’d like to point out that I didn’t use the term SSL. At this point in time, well-hardened web servers shouldn’t be using SSL, they are actually using TLS. SSL has become synonymous with HTTPS and that’s just wrong, and even SSL vs TLS is not a completely equitable comparison. HTTPS means the connection is secure, SSL or TLS is the method used to secure the connection.