Set up Pi-Hole to Block Ads at the Network Level

You can use a Respberry Pi to block all ads coming in at a DNS level

Requirements:

  • CanaKit (provides Raspberry Pi, Power Supply)
  • Short ethernet cable
  • Monitor and keyboard for initial setup

Raspberry Pi Initial Installation and Configuration

Assemble and then plug in the Raspberry Pi, which should take you to the NOOBS setup window.

Install Raspbian, and work through until you get to the desktop screen for the Raspberry Pi.

Router Configuration

With the Raspberry Pi configured, connect to your router admin page and find the IP address of the Raspberry Pi. Assign a static IP to the Raspberry Pi.

Pi-Hole Installation

On the Raspberry Pi, run the following commands to start installation:

wget -O basic-install.sh https://install.pi-hole.net
sudo bash basic-install.sh

When asked for Upstream DNS Provider, select Cloudflare.

For everything else, just select the default options.

After installation finishes, you’ll be able to log into the web admin with the password provided at the end of installation.

You may want to change the password used to login, you can run the following command on the Raspberry Pi:

pihole -a -p

Pi-Hole Configuration

Access the web interface using http://<IP_ADDRESS>/admin, and log in using the password above.

Change the DNS server on your router to the IP address above.

Verification

An easy way to verify is to check a page that uses ads (here’s a good example). See if any ads appear, and then check the Pi-Hole admin:

Reference

https://kb.netgear.com/25722/How-do-I-reserve-an-IP-address-on-my-NETGEAR-router

https://cleanbrowsing.org/guides/cleanbrowsing-netgear-orbi-dns-configuration

Changing Default Token Expiration for Azure AD

To change the default token expiration timeframe when using Azure AD for authentication, you can do the following.

First, if you haven’t yet, install the AzureADPreview PowerShell Module:

Install-Module AzureADPreview

Now, connect to Azure AD using an account that has access to manage App Registrations:

Connect-AzureAD

After that, check and delete any policies that currently exist

After that, create a new policy (this one is set for 30 minutes as an example):

$policy = New-AzureADPolicy -Definition @('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"00:30:00","MaxAgeSessionSingleFactor":"00:30:00"}}') -DisplayName "CustomPolicy" -IsOrganizationDefault $false -Type "TokenLifetimePolicy"

And apply that policy to the service principal tied to the Azure AD integration:

$sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '<service principal display name>'"

Add-AzureADServicePrincipalPolicy -Id $sp.ObjectId -RefObjectId $policy.Id

Now verify that the policy is in place:

Reference

https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-configurable-token-lifetimes

Converting P7B Certificates into PFX Certificates

To convert a P7B certificate into a PFX certificate, you’ll need the following:

  • The .p7b certificate created after the CSR is generated.
  • The private key (likely .pem or .key) generated when generating the CSR.

First, doublr click the .p7b file and export out all of the certs that appear in Certificate Manager as Base64 encoded .CER files:

Once this is done, you’ll be able to create the .PFX file with the following openssl command:

openssl pkcs12 -export -in certificatename.cer -inkey privateKey.key -out certificatename.pfx -certfile cacert1.cer -certfile cacert2.cer 

Generating CSRs the Easy Way In Windows

First, download the DigiCert Certificate Utility.

Afterwards, click on “Create CSR”, and fill out the appropriate information to generate the CSR.

Accessing the Private Key for the CSR

When a CSR is generated, there is a private key that associates with the CSR (and eventual certificate). You can access this in certmgr.exe:

To get the private key, go to ‘All Tasks’ -> ‘Export’ and export the private key as needed. Windows will export it as a .pfx file, which you can convert using openssl:

openssl pkcs12 -in exported_cert.pfx  -nocerts -out key.pem

When requested for the password and PEM passphrase, use the password provided in the export step above.

Adding Icons to an Angular Web Site

Changing out the icons for an Angular website is just a few steps. This guide assumes you have an icon already in place, preferably in PNG format.

First, use a tool like Real Favicon Generator to create the source files, which will include a favicon.ico file alongside a series of apple-touch-icon* files. Add these files to the /src directory.

After that, make the following change to your index.html file:

<head>
    ...
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
    <link rel="manifest" href="/site.webmanifest">
    <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
    <meta name="msapplication-TileColor" content="#da532c">
    <meta name="theme-color" content="#ffffff">
</head>

And finally make the change to angular.json to ensure the files are delivered correctly on build:

"assets": [
            ...
            "src/android-chrome-192x192.png",
            "src/android-chrome-512x512.png",
            "src/apple-touch-icon.png",
            "src/browserconfig.xml",
            "src/favicon-16x16.png",
            "src/favicon-32x32.png",
            "src/mstile-150x150.png",
            "src/safari-pinned-tab.svg",
            "src/site.webmanifest"
          ],

To test that everything worked successfully, make sure to run a build:

ng build

After this finishes, check the /dist folder to see the icon files added in the step above.

Keep A Consumption-Based Function App Warm With A Scheduled Task

With Azure Function Apps using a Consumption plan, they will need to be warmed up if not used for 20 minutes to prevent having cold starts for the users in place. If you’re serving an API using a Function app, you’ll want to put this in place to keep performance ideal.

Something to note with this solution – it works well for low-traffic APIs where the goal is to serve an API using the consumption app for low costs. Assuming larger traffic use, you may be better off switching to a dedicated App Service plan, to prevent the cold start issue at all, because cold starts will still come when scaling out.

To follow this guide, I’ll assume you already have a Function app in place. Create a new function with the following:

using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;

namespace YOUR_NAMESPACE
{
  public static class KeepWarm
  {
    [FunctionName("KeepWarm")]
    public static void Run([TimerTrigger("0 */15 * * * *")]TimerInfo myTimer, ILogger log)
    {
      log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
    }
  }
}

That’s it! After this is deployed, the cold start issue of your API should be removed, as running the log function every 15 minutes will prevent the system from needing a cold start.

Further Reading

https://mikhail.io/2018/05/azure-functions-cold-starts-beyond-first-load/

Adding a Scheduled Task to NOPCommerce through the database

When working in NOPCommerce, you may want to create a scheduled task without having to go through plugin install and uninstall.

First, you’ll have to have a task set up in ideally a plugin.

You can do so by adding the following to the ScheduleTask table in the NOPCommerce database:

INSERT INTO [dbo].[ScheduleTask]
           ([Name]
           ,[Seconds]
           ,[Type]
           ,[Enabled]
           ,[StopOnError]
           ,[LeasedByMachineName]
           ,[LeasedUntilUtc]
           ,[LastStartUtc]
           ,[LastEndUtc]
           ,[LastSuccessUtc])
     VALUES
           (
           'NAME_OF_TASK'
           SCHEDULE_IN_SECONDS
           'NAMESPACE.ClassName, NAMESPACE'
           IS_ENABLED, -- 1-yes, 0-no
           SHOULD_STOP_ON_ERROR, -- 1-yes, 0-no
           NULL,
           NULL,
           NULL,
           NULL,
           NULL
           )
GO

After that’s done, you should be able to immediately run the task.

Adding Swagger UI Documentation to Azure Function APIs

You can set up Swagger UI in your Azure Function API to allow for providing documentation for your serverless API pretty easily.

Initial Configuration

First, add the SwashBuckle library to your process via the <project>.csproj file:

<ItemGroup>
    <PackageReference ... />
    <PackageReference Include="AzureFunctions.Extensions.Swashbuckle" Version="1.4.1" />
    ...
</ItemGroup>

Next set up the SwashBuckle startup code in SwashBuckleStartup.cs:

using System.Reflection;
using AzureFunctions.Extensions.Swashbuckle;
using YOUR_NAMESPACE;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;

[assembly: WebJobsStartup(typeof(SwashBuckleStartup))]
namespace YOUR_NAMESPACE
{
  internal class SwashBuckleStartup : IWebJobsStartup
  {
    public void Configure(IWebJobsBuilder builder)
    {
      builder.AddSwashBuckle(Assembly.GetExecutingAssembly());
    }
  }
}

Now create both HTTP Triggers for the Swagger document:

[FunctionName("SwaggerJson")]
[SwaggerIgnore]
public static Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "json")]
        HttpRequestMessage req,
    ILogger log,
    [SwashBuckleClient] ISwashBuckleClient swashBuckleClient)
{
  return Task.FromResult(swashBuckleClient.CreateSwaggerDocumentResponse(req));
}

And the Swagger UI document:

[FunctionName("SwaggerUI")]
[SwaggerIgnore]
public static Task<HttpResponseMessage> RunUI(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "/")]
        HttpRequestMessage req,
    ILogger log,
    [SwashBuckleClient] ISwashBuckleClient swashBuckleClient)
{
  return Task.FromResult(swashBuckleClient.CreateSwaggerUIResponse(req, "json"));
}

Running this locally will provide two endpoints:

The final step for initial configuration is changing the documentation for the API page. Add the following to host.json:

{
  "version": "2.0",
  "extensions": {
    ...

    "Swashbuckle": {
      "Documents": [
        {
          "Title": "YOUR_TITLE",
          "Version": "v1",
          "Description": "YOUR_DESCRIPTION"
        }
      ]
    }
  }
}

Which will give you:

Further Reading:

https://medium.com/@yuka1984/open-api-swagger-and-swagger-ui-on-azure-functions-v2-c-a4a460b34b55

Cleaning up Hard Drive Space in Linux

If you run into a situation where a Linux machine is running out of space, here’s a way to check what is taking up so much space and clean the server up.

Install ncdu:

sudo apt-get install ncdu

Afterwards, start ncdu and you’ll be presented with an interface that’ll walk you through the directories taking the most space:

Troubleshooting – My Drive is 100% Full

If you’re having trouble installing the above, you likely have a full drive that cannot do anything else. Here are a few steps that may help:

First, see if there are any individual files you can delete to clear some space (such as in the user directory). If this isn’t an option, go into /var/tmp and delete the files in there () this is not always a safe choice).

After that, run the following:

sudo apt-get clean
sudo apt install byobu
sudo purge-old-kernels
sudo apt autoremove
sudo update-grub

With this you should be able to install the above and check into what is taking up so much space.

Getting Slack Notifications with Azure Monitor

An option when trying to get alerts from Azure Monitor (such as web tests, processing alerts and more) is to get them to a Slack channel to allow for an alternative to receiving alerts via email.

Setting up Slack

To start, you’ll need to have a Slack workspace with an available app. If needed, create the channel desired to receive notifications.

Create a new Slack app named “Azure Notifications” and turn on the “Incoming Webhooks” capability, which will provide a URL (webhook) that you’ll use later to receive the notifications from Azure.

Creating Conversion Logic App in Azure

Next, you’ll need to create a logic app in Azure that takes the alert provided from Azure and converts the message into something that can be displayed in Slack.

Create a logic app, open Code View and use the following (replacing the URI provided with the one above):

{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "actions": {
            "Http": {
                "inputs": {
                    "body": {
                        "text": "@{if(equals(triggerBody()?['data']?['essentials']?['monitorCondition'],'Resolved'), ':green_heart:', ':red_circle:')} @{triggerBody()?['data']?['essentials']?['alertRule']}"
                    },
                    "headers": {
                        "Content-Type": "application/json"
                    },
                    "method": "POST",
                    "uri": "YOUR_WEBHOOK_HERE"
                },
                "runAfter": {},
                "type": "Http"
            }
        },
        "contentVersion": "1.0.0.0",
        "outputs": {},
        "parameters": {},
        "triggers": {
            "manual": {
                "inputs": {
                    "schema": {
                        "properties": {
                            "data": {
                                "properties": {
                                    "alertContext": {
                                        "properties": {
                                            "condition": {
                                                "properties": {
                                                    "allOf": {
                                                        "items": {
                                                            "properties": {
                                                                "dimensions": {
                                                                    "items": {
                                                                        "properties": {
                                                                            "name": {
                                                                                "type": "string"
                                                                            },
                                                                            "value": {
                                                                                "type": "string"
                                                                            }
                                                                        },
                                                                        "required": [
                                                                            "name",
                                                                            "value"
                                                                        ],
                                                                        "type": "object"
                                                                    },
                                                                    "type": "array"
                                                                },
                                                                "metricName": {
                                                                    "type": "string"
                                                                },
                                                                "metricNamespace": {
                                                                    "type": "string"
                                                                },
                                                                "metricValue": {
                                                                    "type": "number"
                                                                },
                                                                "operator": {
                                                                    "type": "string"
                                                                },
                                                                "threshold": {
                                                                    "type": "string"
                                                                },
                                                                "timeAggregation": {
                                                                    "type": "string"
                                                                }
                                                            },
                                                            "required": [
                                                                "metricName",
                                                                "metricNamespace",
                                                                "operator",
                                                                "threshold",
                                                                "timeAggregation",
                                                                "dimensions",
                                                                "metricValue"
                                                            ],
                                                            "type": "object"
                                                        },
                                                        "type": "array"
                                                    },
                                                    "windowSize": {
                                                        "type": "string"
                                                    }
                                                },
                                                "type": "object"
                                            },
                                            "conditionType": {
                                                "type": "string"
                                            },
                                            "properties": {}
                                        },
                                        "type": "object"
                                    },
                                    "essentials": {
                                        "properties": {
                                            "alertContextVersion": {
                                                "type": "string"
                                            },
                                            "alertId": {
                                                "type": "string"
                                            },
                                            "alertRule": {
                                                "type": "string"
                                            },
                                            "alertTargetIDs": {
                                                "items": {
                                                    "type": "string"
                                                },
                                                "type": "array"
                                            },
                                            "description": {
                                                "type": "string"
                                            },
                                            "essentialsVersion": {
                                                "type": "string"
                                            },
                                            "firedDateTime": {
                                                "type": "string"
                                            },
                                            "monitorCondition": {
                                                "type": "string"
                                            },
                                            "monitoringService": {
                                                "type": "string"
                                            },
                                            "originAlertId": {
                                                "type": "string"
                                            },
                                            "resolvedDateTime": {
                                                "type": "string"
                                            },
                                            "severity": {
                                                "type": "string"
                                            },
                                            "signalType": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    }
                                },
                                "type": "object"
                            },
                            "schemaId": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    }
                },
                "kind": "Http",
                "type": "Request"
            }
        }
    }
}

You can test by running the code directly, and see if you get a red circle:

Assuming this is in place, the final step is creating an action group that allows for setting alerts to send to the Slack channel.

Before going to the next step, take note of the webhook provided by the Logic App:

Setting up Action Group

The final step is setting up an action group with the Logic App webhook, while turning on the Common Alert Schema.