Securing Client Credentials Flow with Certificate

Sometimes there is application which requires background jobs to run without user interaction (though in most cases you can manage to avoid using App Permissions) In these particular exceptions using app permissions and client credentials might be justified, especially if you don’t want to maintain (and secure) long term storage of user refresh token for non user context operation.

Click the picture for larger version

Prevalent use of Shared Secrets in Client Credentials

I often see Client Credentials used with shared secret, and do understand that for some SaaS integrations with self service of on-boarding the shared secret is the easy way to on-board them. (Although uploading the private key for the particular app is not rocket science either, could be easily used in most of the cases)

Why Certificate is better than Shared Secret

Certs and Secrets show in Azure AD Application
  • Certificate Credentials never transmit the plain-text secret when requesting Access Tokens from Azure AD. Instead they transit JWT token which is signed with private key which the app holds. Verification is asymmetric, so Azure AD holds only the key which can assert that the JWT token came from the party in posession of the private key
  • Shared Secret in essence is weaker verification method (String vs Certificate)
  • You have more established ways to protect the certificate, than a single string

Alternatives to a certificate

  • You can use API management in conjunction with JWT-Bearer flow to gain better control of Shared Secret of 3rd parties.
    • This could be done by restricting caller IP’s and using several policies concerning the use Partners given access to Client Crendentials. In this scenario API management forwards the token once policies are checked
  • You could put short lived (few days) Client Secret in a Key Vault, and authorize the Key Vault answer only to a certain trusted IP’s… Still…once the plain text is exposed in the code run time the Client Secret is out of Key Vaults Domain until the client secret expires
    • Generally Client Secrets aren’t meant to be used as short lived
https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/overview
  • … And if the API doesn’t need App Permissions, you can use Conditional Access…
  • You shouldn’t use real user service account as service account just get conditional access. Using a user principal instead of actual service principal opens another set of problems, which this blog is too short to delve on

Enable NodeJS API for Client Credentials with Certificate

Before proceeding using App Permissions, or Shared Secret in any other flow just check that your scenario is not one of the below Examples

  1. App Permissions used in flows that could’ve used delegated permissions using users token context
  2. Mobile Client Using Client Secret as part of Authorization Code Flow (Mobile client is not confidential client, lets just leave it there)
  3. (Not directly related, but a concern) App Permissions are required by multi-tenant app
    • In most scenarios you can create additional single tenant app, besides the registered multi-tenant to retain control of the shared secret (revokation etc).
      • Giving external multi-tenant app permissions is something you should think hard before proceeding in the first place

Create Certificate Credentials for existing App Registration

Pre-hardening

  • Ensure Application doesn’t have any redirect URI’s. This effectively ensures no user sign in process can get tokens returned for the Application
  • Remove the default delegated user permissions from the app
  • Ensure Implicit Grant isn’t enabled in the application (This wouldn’t work any way with the user permissions removed to sign-in and read user profile , but we do some additional cleaning here)
  • Remove Any password credentials app might have (obviously, if they are used production, dont remove them until the flow is updated to use certificate in code for these apps)

Pre-reqs

  • OpenSSL binaries
  • Azure AD PowerShell Module
$Subject = "CN=" + "DemoApplication2"
$Expiration = (get-date).AddYears(2)
$pass = Read-Host -Prompt "PFX exporting Password"
$cert = New-SelfSignedCertificate -CertStoreLocation "Cert:\CurrentUser\My" -Subject $Subject -KeySpec KeyExchange -NotAfter $Expiration
$AADKeyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
$cert| Export-PfxCertificate -FilePath (($Subject -split "=")[1] + ".pfx") -Password ($pass | ConvertTo-SecureString -AsPlainText -Force) 
#Navigate to directory where OpenSSL is installed
.\openssl.exe pkcs12 -in (($Subject -split "=")[1] + ".pfx") -passin "pass:$pass"  -out (($Subject -split "=")[1] + ".pem") -nodes
$data = get-content (($Subject -split "=")[1] + ".pem")
$data[$data.IndexOf("-----BEGIN PRIVATE KEY-----")..$data.IndexOf("-----END PRIVATE KEY-----")] | Out-File (($Subject -split "=")[1] + ".pem") -encoding "DEFAULT"
connect-azuread
$application = New-AzureADApplication -DisplayName ($Subject -split "=")[1]
New-AzureADApplicationKeyCredential -ObjectId $application.ObjectId -CustomKeyIdentifier ($Subject -split "=")[1] -Type AsymmetricX509Cert -Usage Verify -Value $AADKeyValue -EndDate ($Expiration | get-date -format "dd.M.yyyy" )

NodeJS Code Example using ADAL and Certificate Credentials

  • In the code fill the thumprint, clientID (appid), and your tenant name
//Gets Access Token by sending private key signed JWT Token to Azure AD Token Endpoint 
//https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials
//https://www.npmjs.com/package/adal-node
const {AuthenticationContext,Logging} = require("adal-node")
//set logging level to verbose, and print to console 
Logging.setLoggingOptions({
    log: (level, message, error) => {
      console.log(message)
    },
    level: Logging.LOGGING_LEVEL.VERBOSE,
    loggingWithPII: true
  });
//Token Request Options
  var options = {
    URLwithTenant:"https://login.windows.net/dewired.onmicrosoft.com",
    resource:"https://graph.microsoft.com", 
    applicationId : "c81ea829-d488-46c2-8838-68dde9052478",
    certThumbPrint:"72B807BE590A73E0B88947C902C5C58E22344C5F"
  }
//Construct the Authentication Context
var context = new AuthenticationContext(options.URLwithTenant);
//Read Certificate from buffer to UTF8 string 
const fs = require("fs")
var keyStringFromBuffer = fs.readFileSync("DemoApplication2.pem").toString("UTF8")
console.log(keyStringFromBuffer)
//acquireToken
context.acquireTokenWithClientCertificate(options.resource,options.applicationId,keyStringFromBuffer,options.certThumbPrint,(error,tokenresponse) => {
console.log(error,tokenresponse)
})

In the end you should see verbose message with token returned in the callback for tokenResponse.

If you see error about the self signed cert, ensure that all localization settings match UTF8, and that there are no empty space characters in the PEM file. If you still see the error, copy the Private Key manually from the openSSL created file

Br, Joosua

Hardening SalesForce Integration in Azure Logic Apps + Azure Secure Devops Kit Alignment of Logic Apps

What are logic apps? Azure Logic Apps could be described as a convenient way to ”commoditize” input and out operations between multiple resources and API’s within Azure’s 1st party and third party ecosystem.

Where are Logic Apps used? As far as I’ve seen Logic apps are quite popular with architecture modernization projects where the architecture is leaning towards ”pluggable/decoupled” microservice architecture. Logic Apps can consume and push data to 1st and 3rd party services, which sometimes completely obsoletes previous built in API consumer client from a monolithic app – Salesforce integration is good example of this.

Logic apps support parallel flows, and different clauses to proceed, this is just the app I created for testing which works in more linear fashion…

But Isn’t app managed by Microsoft?

The fact that Logic App abstracts lot of the plumbing doesn’t mean it doesn’t have rich set of optional security features. We are exploring common ones for these.

Besides what I present here there is something called Logic Apps ISE (Integrated Security Environment), but that is separate concept, that is tailored to more specific network and data requirements.

While there aren’t particular best practices, this guide attempts to combine some of the AZSK controls and experiences from integrating SF into Logic Apps

But why harden Salesforce side, isn’t this Azure related security configuration

When you have integrations going across environments and clouds, you have to look at the big picture. Especially when the master data that you process is sometimes the data in SalesForce you need to ensure, that source of the data is also protected… Not just where the data is going to

There are similar recommendations on the AZSK kit, but this combines the IP restrictions + and reduces the amount of accounts that have access to the integration in SF side.

Logic App connectors must have minimum required permissions on data source
This ensures that connectors can be used only towards intended actions in the Logic App
Medium

Checklist

Before proceeding

  • While I am very comfy with building access policies in Azure, I cant claim the same competence in SF -> If you notice something that might cause a disaster, please add comments to this blog, or dm at twitter)
    • hence the Disclaimer: The information in this weblog is provided “AS IS” with no warranties and confers no rights.
    • Do any tests first in a ”NEW” developer account. Don’t ruin your uat/test accounts :)… (Get account here)
    • Make sure that put the IP restrictions under new cloned profile. Not a profile that is currently used
    • For any IP restrictions you set remember to include one IP which you can terminate to (VPN ip etc). This is to ensure that you wont lockout yourself of the SF environment.

Expected result

Once you’ve configured the setup (setup guide below), and tested failure and success behavior the end result you should show failed and success events.

After adding the Logic App IP’s the result should be success.

I can’t emphasize the importance of testing both failing and success behavior when testing any access policy.

Azure Side Configuration

  • Enable Azure Monitoring of the API connection for Azure Control Plane Actions
  • If you want to reduce the output for reader level roles of possibly confidential inputs/outputs in the Logic Apps, then enable Secure Inputs/Outputs setting for the Logic App
  • If your using storage account as part of your logic app flow , ensure storage account logging is enabled
1.0;2020-02-20T07:33:25.0496414Z;PutBlob;Success;201;13;13;authenticated;logicapp;logicapp;blob;"https://logicapp.blob.core.windows.net:443/accounts/Apparel";"/logicapp/accounts/Apparel";

SalesForce Side

  • Profiles in Users
    • Clone, or create new profile for Logic Apps integration.
      • I cloned the System administrator profile, but more granular setup is possibly available by configuring the least privilege permissions for the application
    • Under ’Login IP ranges’ add the Azure Logic App IP ranges that can be found from properties (Include the user IP also which you will use for registering the API in the Logic App)
undefined

  • The SF integration connection ’API connection’ in Azure runs in the context of the user registering the API first time in logic apps
    • There is no point to allow non related users of creating OAuth2 tokens bearing the apps context
  • Oauth Policies in App Manager
    • For ’Permitted users’, change setting to ’Admin approved users are pre-authorized’
    • For ’IP Relaxation’ change setting to ’Enforce IP restrictions’
  • Profiles in App Manager
    • Add the profile created in previous step to profiles
      • This step restricts the users that can use the integration to a custom profile, or system administrators profile
  • In Security ’Session Management’ Ensure IP restrictions aren’t just enforced on login by selecting this setting
    • This setting applies based on the profile IP restriction settings, not globally unless all your profiles have IP login restrictions in place
IP Restrictions setting SF documentation
  • After this revoke all existing user tokens for the app under user settings and Oauth connected Apps
  • Reauthorize the connection
  • Now test the integration to work

Further AZSK recommendations

  • This blog adds the non-Azure side recommendation to hardening guidelines.
  • The following guidelines are where you should start, if you start hardening your Logic Apps
  • if your logging data in Logic Apps (which is recommended to detect misuse and doing debugging: Understand which and what kind of data (PII) etc will be stored/persisted in the logs outside possibly standard access and retention policies.
    • If you want to separate part of the data create separate logic app with secure inputs/outputs, or implement secure inputs/outputs for the current application

Source https://github.com/azsk/DevOpsKit-docs/blob/master/02-Secure-Development/ControlCoverage/Feature/LogicApps.md

LogicApps

Description & Rationale ControlSeverity Automated Fix Script
Multiple Logic Apps should not be deployed in the same resource group unless they trust each other
API Connections contain critical information like credentials/secrets, etc., provided as part of configuration. Logic App can use all API Connections present in the same Resource Group. Thus, Resource Group should be considered as security boundary when threat modeling.
High Yes No
Logic App connectors must have minimum required permissions on data source
This ensures that connectors can be used only towards intended actions in the Logic App
Medium No No
All users/identities must be granted minimum required permissions using Role Based Access Control (RBAC)
Granting minimum access by leveraging RBAC feature ensures that users are granted just enough permissions to perform their tasks. This minimizes exposure of the resources in case of user/service account compromise.
Medium Yes No
If Logic App fires on an HTTP Request (e.g. Request or Webhook) then provide IP ranges for triggers to prevent unauthorized access
Specifying the IP range ensures that the triggers can be invoked only from a restricted set of endpoints.
High Yes No
Must provide IP ranges for contents to prevent unauthorized access to inputs/outputs data of Logic App run history
Using the firewall feature ensures that access to the data or the service is restricted to a specific set/group of clients. While this may not be feasible in all scenarios, when it can be used, it provides an extra layer of access control protection for critical assets.
High Yes No
Application secrets and credentials must not be in plain text in source code (code view) of a Logic App
Keeping secrets such as DB connection strings, passwords, keys, etc. in clear text can lead to easy compromise at various avenues during an application’s lifecycle. Storing them in a key vault ensures that they are protected at rest.
High Yes No
Logic App access keys must be rotated periodically
Periodic key/password rotation is a good security hygiene practice as, over time, it minimizes the likelihood of data loss/compromise which can arise from key theft/brute forcing/recovery attacks.
Medium No No
Diagnostics logs must be enabled with a retention period of at least 365 days.
Logs should be retained for a long enough period so that activity trail can be recreated when investigations are required in the event of an incident or a compromise. A period of 1 year is typical for several compliance requirements as well.
Medium Yes No
Logic App Code View code should be backed up periodically
Logic App code view contains application’s workflow logic and API connections detail which could be lost if there is no backup. No backup/disaster recovery feature is available out of the box in Logic Apps.
Medium No No

Source https://github.com/azsk/DevOpsKit-docs/blob/master/02-Secure-Development/ControlCoverage/Feature/APIConnection.md

APIConnection

Description & Rationale ControlSeverity Automated Fix Script
Logic App connectors must use AAD-based authentication wherever possible
Using the native enterprise directory for authentication ensures that there is a built-in high level of assurance in the user identity established for subsequent access control. All Enterprise subscriptions are automatically associated with their enterprise directory (xxx.onmicrosoft.com) and users in the native directory are trusted for authentication to enterprise subscriptions.
High Yes No
Data transit across connectors must use encrypted channel
Use of HTTPS ensures server/service authentication and protects data in transit from network layer man-in-the-middle, eavesdropping, session-hijacking attacks.
High Yes No

Br Joosua!

Measuring Node Execution In VSCode with PowerShell

Background

Forcing synchronous execution in NodeJS some times increases execution time of the function, because you want to ensure certain order of progress in the code.

There are many good reads into the subject. TL DR: In my opinion you can reduce the amount of code quite drastically in particular select cases by introducing synchronous behavior into your code.

Timing functions

Generally you can measure functions execution time setTimeOut() and using dates for calculation. There are also many wrapper solutions for much more fine grained measurements/telemetry etc.

Since PowerShell is my default console for VScode I tend to use native functions of Powershell often to speed up some things – And this is where the Measure-Command function comes into play

Non blocking function

Blocking function

//Non Blocking function
const req = require('request-promises')
var uris =[
    "https://uri1.com",
    "https://uri2.com",
    "https://uri3.com",
    "https://uri4.com"
]
 function LoopAsync (uris) {
    for (let index = 0; index < uris.length; index++) {
        const element = uris[index];
        req(element).then((result) => {
            console.log(element + ".>" + result.headers.server)
        })
    }
}
LoopAsync(uris)

//Blocking function
const req = require('request-promises')
var uris =[
    "https://uri1.com",
    "https://uri2.com",
    "https://uri3.com",
    "https://uri4.com"
]
async function LoopAsyncAwait (uris) {
    for (let index = 0; index < uris.length; index++) {
        const element = uris[index];
        const data = await req(element)
        console.log(element + ".>" + data.headers.server)
    }
}
LoopAsyncAwait(uris)

Example for VScode

Measure-Command {node .\AsyncLoop.js}
Measure-Command {node .\syncLoop.js}