Pages

Thursday, November 12, 2026

Locking down Azure Logic Apps Standard

Locking down Azure Logic Apps Standard with private endpoints is a common enterprise requirement. It's also an easy way to break the app with an error message that points you in the wrong direction. This post walks through a real troubleshooting session, the root cause, and a checklist you can reuse.

The setup

The goal was a fully private Logic App Standard:

  • Inbound: the Logic App is reachable only through a private endpoint, with public access disabled.
  • Outbound: the Logic App reaches its storage account only through VNet integration and storage private endpoints, with storage public access disabled.
  • Hosting: Workflow Standard (WS1) plan, Windows.

The symptom

As soon as public network access on the storage account was disabled, the Logic App broke:

  • The Overview blade showed System.Private.CoreLib: Access to the path 'C:\home\site\wwwroot\host.json' is denied.
  • Runtime version showed Error.
  • Saving a workflow failed with Encountered an error (ServiceUnavailable) from host runtime.
  • Nobody on the team could create or edit workflows.

"Access denied" suggests a permissions problem, but it isn't one. Turning storage public access back on made everything work instantly. That one test isolated the fault to the private network path between the app and its storage.

Why storage matters so much

Logic App Standard runs on the Azure Functions runtime, and it needs all four storage services just to start:

Service Used for
FileThe C:\home content share (host.json, workflow definitions), mounted over SMB
BlobRuntime artifacts and state
QueueTrigger and job orchestration
TableRun history, workflow state, locks

If any of these can't be reached, the host doesn't start. With storage locked down, there's no public fallback.

Key point: a private endpoint is inbound only

A private endpoint on the Logic App controls who can call the app. It does nothing for traffic the app sends out. For the runtime to reach private storage you also need:

  1. VNet integration on a dedicated subnet delegated to Microsoft.Web/serverFarms.
  2. Route All, so outbound traffic goes through the VNet.
  3. Content storage over VNet, so the file share mounts through the VNet.
  4. Private endpoints for blob, file, queue and table, each with a DNS record.

Also note that an App Service plan can't have a private endpoint. Private endpoints go on the apps and on the storage account.

Step 1: Configure outbound routing

In the portal, go to Logic App → Settings → Networking → Virtual network integration and click the subnet link. Then:

  • Application routing → Outbound internet traffic: on (Route All)
  • Configuration routing → Content storage: on

If those checkboxes aren't shown, add these app settings instead:

WEBSITE_VNET_ROUTE_ALL = 1
WEBSITE_CONTENTOVERVNET = 1

Step 2: Check the storage prerequisites

  • Storage account → Settings → Configuration → Allow storage account key access: Enabled. The content share mount uses the storage key. If Azure Policy disables shared key access, the mount fails no matter how good your networking is.
  • WEBSITE_RUN_FROM_PACKAGE isn't set. It makes wwwroot read-only.
  • The file share named in WEBSITE_CONTENTSHARE exists.

Step 3: Test DNS from Kudu

Open Kudu from a jump box or over VPN, since the app is private. For regional hostnames the URL looks like this:

https://<app>-<hash>.scm.<region>-01.azurewebsites.net/DebugConsole

ping and nslookup are blocked in the App Service sandbox. Use the built-in tools instead:

nameresolver <storage>.blob.core.windows.net
nameresolver <storage>.file.core.windows.net
nameresolver <storage>.queue.core.windows.net
nameresolver <storage>.table.core.windows.net

This was the result for blob:

nameresolver <storage>.blob.core.windows.net
  -> CNAME <storage>.privatelink.blob.core.windows.net
  -> no Address returned

nameresolver <storage>.privatelink.blob.core.windows.net
  -> Non-existent domain

That was the root cause. The privatelink.blob.core.windows.net zone was linked to the VNet but had no A record for the storage account. Because the zone is authoritative, it returned NXDOMAIN instead of falling back to the public IP, so the runtime couldn't resolve storage at all.

Step 4: Fix the private DNS records

For each storage private endpoint, go to Private endpoint → Settings → DNS configuration → Add configuration and select the privatelink.<service>.core.windows.net zone that's linked to your VNet. This creates the A record and keeps it managed.

Then check each zone:

  • Recordsets contain your storage account, pointing to a private IP.
  • Virtual network links include the VNet used for integration.

After the fix, all four services resolved to private IPs:

Service Private IP
blob10.0.1.4
file10.0.1.5
queue10.0.1.6
table10.0.1.7

Step 5: Test connectivity

tcpping <storage>.blob.core.windows.net:443
tcpping <storage>.queue.core.windows.net:443
tcpping <storage>.table.core.windows.net:443

All three connected.

Watch out for this one: tcpping to file:445 from Kudu always fails with "An attempt was made to access a socket in a way forbidden by its access permissions" (Windows error 10013). This isn't your network. The App Service sandbox blocks outbound ports 445, 137, 138 and 139 for in-app processes, and the platform mounts the content share outside the sandbox. To test 445 on the network, run this from a VM instead:

Test-NetConnection 10.0.1.5 -Port 445

Step 6: Restart cleanly

Restart the Logic App. If Runtime version still shows Error, do a full Stop, wait 30 seconds, then Start. That forces the content share to remount with the corrected DNS. Once Runtime version shows ~4, set storage public access back to Disabled.

Lessons learned

  • "Access denied" on host.json usually means the content share isn't mounted. It's rarely a real permissions issue.
  • Check all four storage services, not just blob and file. Queue and table are just as critical.
  • Watch for duplicate privatelink.* zones in hub and spoke setups. The record has to be in the zone that's linked to the integration VNet.
  • If your VNet uses custom DNS, forward privatelink.* to 168.63.129.16 or to an Azure Private DNS Resolver.
  • Plan for developer access. Designer edits in the portal go through ARM and work from anywhere. Run history inputs and outputs, HTTP trigger testing and Kudu need line of sight into the VNet, through a jump box, VPN or a self-hosted CI/CD agent.
  • Use the right tools in Kudu: nameresolver and tcpping, not nslookup and ping.

Reusable checklist

  • ☐ VNet integration on a dedicated, delegated subnet
  • ☐ Route All and Content storage turned on
  • ☐ Storage private endpoints for blob, file, queue and table, each with a DNS zone group
  • ☐ Private DNS zones linked to the integration VNet, with A records present
  • ☐ nameresolver returns private IPs for all four services
  • ☐ Shared key access enabled; content share exists; no WEBSITE_RUN_FROM_PACKAGE
  • ☐ Storage public access disabled, app restarted, Runtime version shows ~4

In private networking, when something fails, check DNS first.

Saturday, April 11, 2026

How to Record Teams & Chrome Audio on macOS

How to Record Teams & Chrome Audio on macOS

Recording Teams, Chrome & Mic on macOS

Recording professional meetings or technical demos on a Mac is notoriously tricky because macOS doesn't allow internal audio capture by default. This guide shows you how to use BlackHole 2ch to route audio perfectly.

The Goal

Capture Teams audio, Chrome audio, and your Jabra Mic simultaneously while hearing everything through your headset.


Step 1: Create the "Ear" Device (Multi-Output)

This allows sound to play in your headset and go to the recorder at the same time.

  • Open Audio MIDI Setup (Applications > Utilities).
  • Click the + icon and select Create Multi-Output Device.
  • Check BlackHole 2ch (First) and Jabra Headset (Second).
  • Set Master Device to Jabra Headset.
  • Check Drift Correction for BlackHole 2ch.

Step 2: Create the "Record" Device (Aggregate)

This merges your mic and system audio into one virtual stream.

  • Click + and select Create Aggregate Device.
  • Check BlackHole 2ch (Channels 1-2).
  • Check Jabra Headset (Channel 3).
  • Check Drift Correction for the Jabra.

Step 3: App Configuration

Application Setting Selection
macOS System Sound Output Multi-Output Device
Microsoft Teams Speaker Multi-Output Device
Microsoft Teams Microphone Jabra Headset (Direct)
Recording App (QuickTime) Microphone Input Aggregate Device
⚠️ Pro-Tip: The Volume Lock
macOS disables volume keys for Multi-Output devices. Set your Jabra volume to 100% before switching the system output to the Multi-Output device to ensure the recording isn't quiet.

Troubleshooting: Silent Recordings?

If your level meters aren't moving in QuickTime or Audio MIDI Setup, try the "Nuclear Reset":

Restart the Audio Engine:
Open Terminal and type: sudo killall coreaudiod.
This force-restarts the macOS audio daemon and usually clears any "clogged" virtual pipes.

Note: Always verify your settings in System Settings > Privacy > Microphone to ensure your recording app has permission to access the audio streams.

Saturday, February 28, 2026

MX Master 3S - Mac-like Button Configuration on Fedora Linux

OVERVIEW

--------

Configured the Logitech MX Master 3S mouse to mimic default macOS

button behavior using logiops — a userspace driver for Logitech devices.


PREREQUISITES

-------------

- logiops package installed:

    sudo dnf install logiops


- Enable and start the logid service:

    sudo systemctl enable logid

    sudo systemctl start logid



BUTTON MAPPING (Mac → Linux equivalent)

----------------------------------------

  Thumb button (tap)       → GNOME Activities overlay  (like Mission Control)

  Thumb + swipe Up         → Show all windows           (like Exposé)

  Thumb + swipe Down       → Show desktop               (Super+D)

  Thumb + swipe Left       → Switch to left workspace   (like swipe between desktops)

  Thumb + swipe Right      → Switch to right workspace

  Back button              → Browser / app Back

  Forward button           → Browser / app Forward

  Button behind scroll     → Toggle SmartShift (ratchet ↔ free-spin)

  Scroll wheel             → Smooth hi-res scroll       (like macOS smooth scroll)

  SmartShift               → Auto mode-switch at threshold 30



STEPS

-----


1. Backup existing config (if any):

   sudo cp /etc/logid.cfg /etc/logid.cfg.bak



2. Write the new config to /etc/logid.cfg:

   sudo nano /etc/logid.cfg

   (or use the config block below)



3. Paste the following configuration:


-------- /etc/logid.cfg --------

devices: (

{

  name: "MX Master 3S";


  smartshift:

  {

    on: true;

    threshold: 30;

  };


  hiresscroll:

  {

    hires: true;

    invert: false;

    target: false;

  };


  dpi: 1750;


  buttons: (

    {

      # Gesture/Thumb button

      cid: 0xc3;

      action =

      {

        type: "Gestures";

        gestures: (

          {

            direction: "None";

            mode: "OnRelease";

            action =

            {

              type: "Keypress";

              keys: ["KEY_LEFTMETA"];

            };

          },

          {

            direction: "Up";

            mode: "OnRelease";

            action =

            {

              type: "Keypress";

              keys: ["KEY_LEFTMETA", "KEY_W"];

            };

          },

          {

            direction: "Down";

            mode: "OnRelease";

            action =

            {

              type: "Keypress";

              keys: ["KEY_LEFTMETA", "KEY_D"];

            };

          },

          {

            direction: "Left";

            mode: "OnRelease";

            action =

            {

              type: "Keypress";

              keys: ["KEY_LEFTCTRL", "KEY_LEFT"];

            };

          },

          {

            direction: "Right";

            mode: "OnRelease";

            action =

            {

              type: "Keypress";

              keys: ["KEY_LEFTCTRL", "KEY_RIGHT"];

            };

          }

        );

      };

    },

    {

      # Back button

      cid: 0x53;

      action =

      {

        type: "Keypress";

        keys: ["KEY_BACK"];

      };

    },

    {

      # Forward button

      cid: 0x56;

      action =

      {

        type: "Keypress";

        keys: ["KEY_FORWARD"];

      };

    },

    {

      # Button behind scroll wheel

      cid: 0xc4;

      action =

      {

        type: "ToggleSmartshift";

      };

    }

  );

});

-------- end of config --------



4. Restart the logid service to apply:

   sudo systemctl restart logid



5. Verify no errors:

   journalctl -u logid -n 20 --no-pager


   Expected output (no WARN/ERROR lines):

     [INFO] Device found: MX Master 3S on /dev/hidrawX:255



BUTTON CID REFERENCE (MX Master 3S)

-------------------------------------

  0xc3  → Thumb / Gesture button

  0xc4  → Button behind scroll wheel (mode shift)

  0x53  → Back button

  0x56  → Forward button



NOTES

-----

- KEY_LEFTSUPER is NOT a valid keycode in logiops — use KEY_LEFTMETA instead.

- If the mouse is not detected, check: journalctl -u logid -f

- Config file location: /etc/logid.cfg

- Backup location:      /etc/logid.cfg.bak

- logiops docs:         https://github.com/PixlOne/logiops