• Custom Root Trust with HttpClient in .NET

    Custom root trust is a security practice where an application or system is configured to trust only a specific set of root certificates. This approach restricts the application’s trust to a limited, predefined list of Certificate Authorities, enhancing security by preventing reliance on external or unverified certificates. The practise is also use in a corporate setting to limit trust to an internal root certificate.

    Custom root trust is similar to, but differs from Certificate Pinning, where Certificate Pinning is the act of trusting a specific certificate and custom root trust is trusting all certificates issued by the trusted root certificate.

    Implementing custom root trust has been pretty straight forward with HttpClient ever since .NET 5.

    X509Certificate2 trustedRoot = new X509Certificate2("corp-root-ca.pem");
    
    HttpClientHandler handler = new()
    {
        ServerCertificateCustomValidationCallback = (_, certificate, chain, errors) =>
        {
            // If errors are present, but they are not Remote Certificate Chain Errors, fail the validation.
            if ((errors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
            {
                return false;
            }
    
            // Add intermediate certificates from the server's chain for the verification process.
            foreach (X509ChainElement intermediateElements in chain.ChainElements.Skip(1))
            {
                chain.ChainPolicy.ExtraStore.Add(intermediateElements.Certificate);
            }
    
            // Clear any potential existing trusted root certificates.
            chain.ChainPolicy.CustomTrustStore.Clear();
            // Enforce Custom Root Trust.
            chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
            // Add desired root certificate.
            chain.ChainPolicy.CustomTrustStore.Add(trustedRoot);
            
            // Verify the validity of the chain of trust and the server's certificate
            return chain.Build(certificate);
        }
    };
    HttpClient client = new(handler);
    HttpResponseMessage response = await client.GetAsync("https://intranet.corp.local");
    
  • Workarounds for creating Azure Hybrid Connections with Terraform

    UPDATE: The bug has been fixed and released in 3.98. The workarounds are no longer needed.

    Due to some bugs in the current Terraform azurerm provider (v3.80.0), certain workarounds are needed to create a working hybrid connection.

    The issue is present for both Function Apps and Web Apps.

    Following the official documentation’s examples, the following code will not work and will result in a seemingly successful deployment, but the hybrid connection will not work. No connection will be able to be established, but everything looks fine in the Azure Portal.

    I have two workarounds for this issue and a workaround idea.

    Workaround 1 - Run a Provisioner after Deployment of Function App Hybrid Connection

    resource "azurerm_function_app_hybrid_connection" "example" {
      function_app_id = azurerm_linux_function_app.example.id
      relay_id        = azurerm_relay_hybrid_connection.example.id
      hostname        = "server.example.local"
      port            = 443
    
      send_key_name = azurerm_relay_hybrid_connection_authorization_rule.example.name
    
      lifecycle {
        ignore_changes = [
          relay_id
        ]
      }
    
      provisioner "local-exec" {
        command = "az functionapp hybrid-connection add --hybrid-connection ${azurerm_relay_hybrid_connection.example.name} --namespace ${azurerm_relay_namespace.example.name} -n ${azurerm_linux_function_app.example.name} -g ${azurerm_resource_group.example.name} --subscription ${var.subscription_id}"
      }
    }
    

    The thing to note in this workaround is that the provisioner will change the relay_id which differs in letter casing from what Terraform expects. This will cause Terraform to see the resource as changed and will recreate the hybrid connection on every run. To prevent this, use the lifecycle meta-argument for ignore_changes on relay_id.

    Workaround 2 - Use a Null Resource and Provisioners to create the Hybrid Connection with Azure CLI

    resource "null_resource" "function_app_hybrid_connection" {
      triggers = {
        function_app_name = azurerm_linux_function_app.example.name
        resource_group_name  = azurerm_resource_group.example.name
        namespace_name = azurerm_relay_namespace.example.name
        hybrid_connection_name = azurerm_relay_hybrid_connection.example.name
        subscription_id = var.subscription_id
      }
    
      provisioner "local-exec" {
        command = "az functionapp hybrid-connection add --hybrid-connection ${self.triggers.hybrid_connection_name} --namespace ${self.triggers.namespace_name} -n ${self.triggers.function_app_name} -g ${self.triggers.resource_group_name} --subscription ${self.triggers.subscription_id}"
      }
    
      provisioner "local-exec" {
        when = destroy
        command = "az functionapp hybrid-connection remove --hybrid-connection ${self.triggers.hybrid_connection_name} --namespace ${self.triggers.namespace_name} -n ${self.triggers.function_app_name} -g ${self.triggers.resource_group_name} --subscription ${self.triggers.subscription_id}"
      }
    }
    

    This workaround will not use the azurerm_function_app_hybrid_connection resource at all, but instead use a null_resource with provisioners to create the hybrid connection with Azure CLI. The method prevents Terraform from seeing changes to the resource and will not recreate the hybrid connection on every run, but nor will it be able to update the hybrid connection if it is changed in the Azure Portal.

    Workaround 3 - The Idea

    This is but an idea and I have not attempted it. It may be possible to make an ARM template and use the azurerm_resource_group_template_deployment to deploy the Function App Hybrid Connection.

    As with Workaround 2, this would not detect the changes to the resource. The benefit though would be having no dependence on Azure CLI.

    Remember the Metadata

    When creating the Hybrid Connection in the Azure Relay, remember to add the metadata endpoint with the value of the hostname and port of the server you want to connect to, otherwise the provisioner will fail and the Azure Portal will not display the information correctly.

    resource "azurerm_relay_hybrid_connection" "example" {
      name                          = "example"
      resource_group_name           = azurerm_resource_group.example.name
      relay_namespace_name          = azurerm_relay_namespace.example.name
      requires_client_authorization = true
    
      user_metadata = jsonencode(
      [
        {
          key   = "endpoint",
          value = "server.example.local:443"
        }
      ])
    }
    
  • Improving the Bambu Studio Update Script for Linux

    UPDATE: Bambu Studio has now been released as a Flatpak and if you run an operating system supporting Flatpak, I would highly suggest using that instead. Huge thanks to hadess and may I suggest thanking him through his wishlist.

    Earlier, I wrote about updating Bambu Studio on Fedora Linux. I have since then made a few improvements, such as checking if a newer version exists.

    #!/bin/bash  
    
    set -euo pipefail
    IFS=$'\n\t'
    
    URL=https://api.github.com/repos/bambulab/BambuStudio/releases
    DISTRO=Fedora # or ubuntu (ubuntu is lowercase on the release page)
    OUTPUT=./BambuStudio.AppImage
    VERSION_FILE="$OUTPUT.ver"
    
    RELEASES=$(curl -s $URL)
    LATEST=$(jq -r .[].tag_name <<< $RELEASES | sort -Vr | head -n 1)
    
    [[ -e $VERSION_FILE ]] && CURRENT=$(cat $VERSION_FILE) || CURRENT=""
    
    if [[ $CURRENT != $LATEST ]]; then
    	DOWNLOAD_URL=$(jq -r ".[] | select(.tag_name == \"$LATEST\") | .assets[] | select(.name | contains(\"$DISTRO\")) | .browser_download_url" <<< $RELEASES)
    	wget -O $OUTPUT $DOWNLOAD_URL
    	echo $LATEST > $VERSION_FILE
    	
    	echo "Bambu Studio is updated to $LATEST."
    else
    	echo "Bambu Studio is already up-to-date."
    fi
    
  • Update Bambu Studio on Fedora Linux

    UPDATE: Consider taking a look at the the improved script here.

    UPDATE 2: Bambu Studio has now been released as a Flatpak and if you run an operating system supporting Flatpak, I would highly suggest using that instead. Huge thanks to hadess and may I suggest thanking him through his wishlist.

    To avoid having to visit GitHub to download new versions of Bambu Studio for Linux, use the below script to download and update your AppImage.

    #!/bin/bash
    
    URL=https://api.github.com/repos/bambulab/BambuStudio/releases
    DISTRO=Fedora # or ubuntu (ubuntu is lowercase on the release page)
    OUTPUT=./BambuStudio.AppImage
    
    wget -O $OUTPUT $(curl -s $URL | jq -r ".[0].assets[] | select(.name | contains (\"$DISTRO\")) | .browser_download_url")
    
    chmod +x $OUTPUT
    
  • Bambu Studio on Fedora Silverblue

    UPDATE: Bambu Studio has now been released as a Flatpak. Huge thanks to hadess and may I suggest thanking him through his wishlist.

    Bambu Studio for Linux is only distributed as an AppImage, not as a Flatpak and when running on Fedora, Bambu Studio requires extra dependencies to be installed.

    This fact makes it challenging to run on an immutable operating system like Fedora Silverblue.

    To workaround this challenge, tools like Silverblue’s built-in Toolbox or Distrobox can be used.

    The following is a distrobox.ini for Distrobox Assemble which will allow you to run Bambu Studio’s AppImage in a container.

    [bambulabs]
    image=fedora-toolbox:38
    additional_packages="fuse fuse-libs"
    additional_packages="mesa-libGL mesa-libGLU"
    additional_packages="wayland-devel wayland-protocols-devel libxkbcommon"
    additional_packages="gtk3 webkit2gtk3"
    additional_packages="gstreamer1 gstreamer1-plugins-base gstreamer1-plugin-openh264"
    additional_packages="mesa-libOSMesa"
    

    I will suggest creating a BambuStudio.desktop in your ~/.local/share/applications to launch the program from your host OS.

    [Desktop Entry]
    Name=Bambu Studio
    GenericName=Bambu Studio
    Comment=Bambu Labs Slicer
    Categories=Utility
    Exec=/usr/bin/distrobox enter  bambulabs -- ~/Applications/BambuStudio.AppImage
    Icon=bambustudio
    Terminal=false
    NoDisplay=true
    Type=Application
    

    This example assumes your Bambu Studio AppImage is located as ~/Applications/BambuStudio.AppImage.