NodeJS Unit File

For future reference, this is an example unit file for running a NodeJS server with systemd. The NodeJS code we use reads from a local MariaDB, so I’ve added a service dependency for the database server.

Create /etc/systemd/system/nodeserver.service

[Unit]
Description=SiteName Node.js Server
Requires=After=mariadb.service

[Service]
ExecStart=/path/to/binary/for/node /path/to/nodeJS/html/server.js
WorkingDirectory=/path/to/nodeJS/html
Restart=always
RestartSec=30
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=nodejs-sitename
User=web-user
Group=www-group

[Install]
WantedBy=multi-user.target

Use systemctl daemon-reload to register the new unit file, then “systemctl start nodeserver.service” to start the service. Assuming everything works properly, use “systemctl enable nodeserver.service” to have the service start on boot.

Biffy Minestrone Soup Recipe

Biffy Minestrone Soup Recipe

Recipe by LisaCourse: DinnerCuisine: ItalianDifficulty: Easy
Servings

6

servings
Prep time

30

minutes
Cooking time

30

minutes

Ingredients

  • 3 Tbsp olive oil

  • 1/2 large onion, chopped

  • 15-20 baby carrots, sliced

  • 4 cloves garlic

  • 4 oz tomato paste

  • 1T chili garlic sauce

  • 1 cup potatoes, diced

  • 1/2 cup corn kernels

  • 1 cup green beans, diced

  • 1 tin cannelloni beans

  • 28 oz petit diced tomatoes

  • 4c broth

  • 2 tsp Italian spices

  • 1/2 tsp black pepper, ground

  • 1 cup elbow macaroni, cooked

Method

  • Put oil in pot and heat until shimmering. Add onion and carrot and saute for a few minutes.
  • Add garlic and heat until fragrant.
  • Stir in spices and heat until fragrant.
  • Add diced potatoes and saute for a minute.
  • Add in diced tomatoes, tomato paste, corn, cannelloni beans, and broth. Cook for 20 minutes
  • Add in green beans and cook for five minutes
  • To serve, add soup to bowl and stir in cooked elbow noodles.

Notes

  • Substitute whatever seasonal vegetables are available for corn and green beans

Chili Seasoning Blend

Chili Seasoning Blend

Recipe by LisaCuisine: MexicanDifficulty: Easy

Ingredients

  • 1/4 cup chili spice

  • 1 tsp roasted garlic powder

  • 1 tsp onion powder

  • 1/2 tsp crushed red pepper flakes

  • 1/4 tsp cayenne pepper

  • 2 tsp smoked paprika

  • 1 tsp oregano

  • 1 Tbsp salt

  • 1 Tbsp ground black pepper

Method

  • Combine all ingredients in a food processor and blend until powered.

Notes

  • I use this blend in my chili recipe and as the spice for taco/burrito fillings.

LDAP Authentication: Python Flask

This is a quick python script showing how the flask-ldap3-login module can be used to authenticate and gather user attribute values

from flask_ldap3_login import LDAP3LoginManager
from ldap3 import Tls
import ssl

config = dict()

config['LDAP_HOST'] = 'ad.example.com'

# Use SSL unless you are debugging a problem. Clear text port is 389 and tls_ctx needs to be removed from add_server call
config['LDAP_USE_SSL'] = True
config['LDAP_PORT'] = 636

# Base DN
config['LDAP_BASE_DN'] = 'dc=example,dc=com'

# User Base DN, prepended to Base DN
config['LDAP_USER_DN'] = 'ou=UserDN'

# Groups Base DN, prepended to Base DN
config['LDAP_GROUP_DN'] = 'ou=SecurityGroupDN'

# Server will be manually added to establish SSL
config['LDAP_ADD_SERVER'] = False

# Domain component of userprincipal name
config['LDAP_BIND_DIRECT_SUFFIX'] = '@example.com'

# Search scope needs to be subtree
config['LDAP_USER_SEARCH_SCOPE'] = "SUBTREE"

# Attributes to return
config['LDAP_GET_USER_ATTRIBUTES'] = ("mail", "givenName", "sn")

# Setup a LDAP3 Login Manager.
ldap_manager = LDAP3LoginManager()

# Init the mamager with the config since we aren't using an app
ldap_manager.init_config(config)

# TLS settings to establish trust without validating CA issuance chain. 
# Can use CERT_REQUIRED and ca_certs_file with path to cacerts that includes issuing chain
tls_ctx = Tls(
    validate=ssl.CERT_NONE,
    version=ssl.PROTOCOL_TLSv1,
    valid_names=[
        'ad.example.com',
    ]
)

ldap_manager.add_server(
    config.get('LDAP_HOST'),
    config.get('LDAP_PORT'),
    config.get('LDAP_USE_SSL'),
    tls_ctx=tls_ctx
)

# Validate credentials
response = ldap_manager.authenticate_direct_credentials('e0012345', 'P@s5w0rdG03sH3re')
print(response.status)
print(response.user_info)

Indoor hops?

Not really — but we’ve had a random week of nightly freezes since the hop plants arrived. Instead of planting them outside and keeping them covered, I’ve got them in the pots I use for seed starting and we’ll get them planted in the middle of this coming week. It was also a bit of an experiment — can you keep hop plants in little pots for a week?

Hops + 2 days

Hops + 5 days

They’re not growing anything like the hops out in the ground that we’re covering at night … but they appear to be doing well. And they should be happy enough until Wednesday when it looks like the cold snap ends.

Garden Expansion

We built a pair of raised beds near our back woods — it was a nicely sheltered location, but too shady to grow much. The space is limited, too; and we wanted to have more garden space. The first step was going to be tilling, but we encountered an old tree stump under the grass. A good bit of excavation later, it was a pine tree with a very twisted root system. And several >3″ diameter roots running both horizontally into the garden area and down farther into the ground. Soooo … that’s a no on tilling a big patch to make a garden.

We have the blocks from the existing raised beds, so we’re creating four one-brick-high raised beds in the front yard. We’re going to compost grass clippings, leaves, kitchen scraps, etc between the beds and create hills for additional garden space next year. We’ll move the soil from the two raised beds into these new ones, and we’ve got some additional compost from the past year.

The first step is deconstructing the existing raised beds

And we’re constructing four new, lower, beds with the blocks

We’ve got two beds completed, and hauled about a third of the blocks for the remaining two

We’ve got to level off the geothermal hill — it’s been settling for a few years, and we can add compost on top if the area happens to fall farther. Unfortunately, a big storm blew in this evening … so we gathered up our stuff and ran for the house!

Pants?

A friend pointed out that pants offer a little protection for me, but they are a lot of protection for you … So we can all stop wearing pants now, right? And protesting stores that require customers to wear pants! Freedom! Liberty!

Blueberries!

I’ve wanted to include a lot of permaculture in our home food production. Permaculture is basically creating a continuously agriculturally productive design. Trees, bushes, perennial plants are used to create a garden that is planted once and harvested for decades. I have some hazelnuts planted, and an apple tree from the locus year planting debacle. My idea is to add something every year instead of buying and planting everything in one season.

This year, I’m adding blueberries. In researching the purchase, we wanted to produce fruit this year (or, worst case, next). This meant buying 3 year-old plants. The local nurseries want 35$ per plant for 1-2 year old plants. Which … yeah, we’re not getting a lot of blueberries that way! I found a few places, though, that sell 3 year-old plants for about 14$ a plant (including shipping). I’ve got ten Hardyblue and ten Blueray plants ordered. We’ll have a lot of blueberries (although I’m sure we’ll lose some plants and won’t actually have 20).

In the future, I want to add pawpaw, raspberries, rhubarb, more apple trees, peach trees, and more hazelnuts.

LDAP Authentication and Authorization: PHP

Blah

<?php
    error_reporting(0);
    #=== FUNCTION ==================================================================
    #      NAME: ldapAuthenticationAndAuthorizationWithAttributes
    #      PARAMETERS:
    #                    $strLDAPHost                   String  LDAP Server URI
    #                    $strUIDAttr                    String  Schema attribute for user ID search
    #                    $strSystemUser                 String  System credential username
    #                    $strSystemPassword             String  System credential password
    #                    $strUserBaseDN                 String  User search LDAP base DN
    #                    $strLogonUserID                String  Input user ID
    #                    $strLogonUserPassword          String  Input user password
    #					 $arrayAttrsToReturn			String	Attributes to be returned
    #                    $strGroupBaseDN                String  (optional) Group search LDAP base DN
    #                    $strGroupNamingAttribute       String  (optional) Schema attribute for group search
    #                    $strMembershipAttr             String  (optional) Schema attribute for group membership
    #                    $strAuthGroup                  String  (optional) Group name
    #     DESCRIPTION: Verify authentication and authorization against AD server.a
    #
    #     RETURNS: array(BindReturnCode, Authorized, array(returnValues))
    #                        BindReturnCode:    -1 indicates LDAP connection failure, -2 indicates system account auth failed, -3 indicates user auth not attempted, >=0 is IANA-registered resultCode values (https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xml#ldap-parameters-6)
    #							NOTE: 0 is successful authentication in IANA-registered resultCode
    #                        Authorized:        0 authorization not attempted, -1 is not a member of the located group, 1 is member of the located group
    #						arrayUserAttributeValues	Array with values of $arrayAttrsToReturn
    #
    #     USAGE: $arrayUserAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", $strInputUserName, $strInputUserPassword, array('givenName', 'sn'), "ou=securitygroups,dc=example,dc=com","cn", "member", "LJRTestGroup")
    #===============================================================================
    function ldapAuthenticationAndAuthorizationWithAttributes($strLDAPHost,$strUIDAttr, $strSystemUser, $strSystemPassword, $strUserBaseDN, $strLogonUserID, $strLogonUserPassword, $arrayAttrsToReturn, $strGroupBaseDN=null, $strGroupNamingAttribute=null, $strMembershipAttr=null, $strAuthGroup=null){
        $arrayAuthResults = array();
        $arrayUserAttributeValues = array();
        // Validate password is not null, otherwise directory servers implementing unauthenticated bind (https://tools.ietf.org/html/rfc4513#section-5.1.2) will return 0 on auth attempts with null password
        if( strlen($strLogonUserPassword) < 1){
            $arrayAuthResults['BindReturnCode'] = -3;
            $arrayAuthResults['Authorized'] = -1;
        }
        else{
            // Connect to the LDAP directory for system ID queries
            $systemDS = ldap_connect($strLDAPHost);
            ldap_set_option($systemDS, LDAP_OPT_PROTOCOL_VERSION, 3);

            if ($systemDS) {
                // Bind with the system ID and find $strLogonUserID FQDN
                $systemBind = ldap_bind($systemDS, $strSystemUser, $strSystemPassword);

                if(ldap_errno($systemDS) == 0){
                    $strLDAPFilter="(&($strUIDAttr=$strLogonUserID))";
                    $result=ldap_search($systemDS,$strUserBaseDN,$strLDAPFilter, $arrayAttrsToReturn);

                    $entry = ldap_first_entry($systemDS, $result);

                    $strFoundUserFQDN= ldap_get_dn($systemDS, $entry);

                    if($strFoundUserFQDN){
                        $userDS = ldap_connect($strLDAPHost);
                        ldap_set_option($userDS, LDAP_OPT_PROTOCOL_VERSION, 3);

                        $userBind = ldap_bind($userDS, $strFoundUserFQDN, $strLogonUserPassword);
                        $arrayAuthResults['BindReturnCode'] = ldap_errno($userDS);

                        ldap_close($userDS);

                        if($arrayAuthResults['BindReturnCode'] == 0){
                        	$objFoundUser = ldap_get_entries($systemDS, $result);
							for($arrayAttrsToReturn as $strAttributeName){
								$arrayUserAttributeValues[$strAttributeName] = $objFoundUser[0][$strAttributeName];

							}
							$arrayAuthResults['AttributeValues'] = $arrayUserAttributeValues;
                            //////////////////////////////////////////////////////////////////////////////////////
                            // If an auth group has been supplied, verify authorization
                            //////////////////////////////////////////////////////////////////////////////////////
                            if($strAuthGroup){
								// Escapes in DN need to be double-escaped or bad search filter error is encountered
                                $strGroupQuery = "(&($strGroupNamingAttribute=$strAuthGroup)($strMembershipAttr=" . str_replace("\\","\\\\", $strFoundUserFQDN) . "))";

                                $groupResult = ldap_search($systemDS,$strGroupBaseDN, $strGroupQuery);
                                $authorisedState = ldap_count_entries($systemDS ,$groupResult);

                                // If a group matching the filter is found, the user is authorised
                                if($authorisedState == 1){
                                    $arrayAuthResults['Authorized'] = 1;
                                }
                                // Otherwise the user is not a member of the group and is not authorised
                                else{
                                    $arrayAuthResults['Authorized'] = -1;
                                }
                            }
                            else{
                                $arrayAuthResults['Authorized'] = 0;
                            }
                            //////////////////////////////////////////////////////////////////////////////////////
                            ldap_close($systemDS);
                        }
                        // If the bind failed, the user has not logged in successfully so they cannot be authorized
                        else{
                            $arrayAuthResults['Authorized'] = -1;

                            ldap_close($systemDS);
                            ldap_close($userDS);
                        }
                    }
                    // User not found in directory
                    else{
                        $arrayAuthResults['BindReturnCode'] = 32;
                        $arrayAuthResults['Authorized'] = -1;
                    }
                }
                // system bind failed
                else{
                    $arrayAuthResults['BindReturnCode'] = -2;
                    $arrayAuthResults['Authorized'] = -1;
                    ldap_close($systemDS);
                }
            }
            // ldap connection failed
            else{
                $arrayAuthResults['BindReturnCode'] = -1;
                $arrayAuthResults['Authorized'] = -1;
            }
        }
        return $arrayAuthResults;
    }

    print "User password not supplied:\n";
    $arrayNullPassword = array();
    $arrayNullPassword = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", '');
    var_dump($arrayNullPassword);

    print "Bad password:\n";
    $arrayBadPassword = array();
    $arrayBadPassword = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'N0tTh3P@s5w0rd',"ou=SecurityGroups,dc=example,dc=com","cn", "member");
    var_dump($arrayBadPassword);

    print "\nInvalid user:\n";
    $arrayUserNotInDirectory = array();
    $arrayUserNotInDirectory = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "xe0012345", 'xDoesN0tM@tt3r');
    var_dump($arrayUserNotInDirectory);

    print "\nGood password without authorization:\n";
    $arrayUserAuthenticated = array();
    $arrayUserAuthenticated = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re');
    var_dump($arrayUserAuthenticated);

    print "\nGood password with authorized user:\n";
    $arrayUserAuthorized = array();
    $arrayUserAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re',"ou=SecurityGroups,dc=example,dc=com","cn", "member", "cfyP_Unix_UnixUsers");
    var_dump($arrayUserAuthorized);

    print "\nGood password with unauthorized user:\n";
    $arrayUserNotAuthorized = array();
    $arrayUserNotAuthorized = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "Sy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re',"ou=SecurityGroups,dc=example,dc=com","cn", "member", "WIN AM Team West");
    var_dump($arrayUserNotAuthorized);

    print "\nBad system account:\n";
    $arrayBadSystemCred = array();
    $arrayBadSystemCred = ldapAuthenticationAndAuthorizationWithAttributes("ldaps://ad.example.com","sAMAccountName","ldapquery@example.com", "xSy5t3mP@ssw0rdG03sH3re", "ou=example,dc=example,dc=com", "e0012345", 'Us3rP@s5w0rdG035H3re|Us3rP@s5w0rdG035H3re');
    var_dump($arrayBadSystemCred);

?>

LDAP Authentication: PHP and Active Directory

This is a very brief function that authenticates a user against Active Directory. Because you can authenticate using a fully qualified DN, sAMAccountName, or userPrincipalName … there’s no need to use a system credential or search for the user provided you’ve got a single domain in your forest (i.e. you know what to prepend to the sAMAccountName or postpend to userPrincipalName).

If you need to perform authorization as well as authentication, you’ll need the user’s FQDN so use the generic LDAP authentication and authorization function.

<?php
    error_reporting(0);
    #=== FUNCTION ==================================================================
    #      NAME: activeDirectoryLDAPAuthentication
    #      PARAMETERS: 
    #                    $strLDAPHost                   String  LDAP Server URI
    #                    $strLogonUserID                String  Input user ID
    #                    $strLogonUserPassword          String  Input user password
    #     DESCRIPTION: Verify authentication againt Active Directory server.
    #     
    #     RETURNS: int BindReturnCode:    -2 indicates LDAP connection failure, -3 indicates user auth not attempted, >=0 is IANA-registered resultCode values (https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xml#ldap-parameters-6)
    #							NOTE: 0 is successful authentication in IANA-registered resultCode
    #
    #     USAGE: $iBindResult = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", $strInputUserName, $strInputUserPassword)
    #===============================================================================
    function activeDirectoryLDAPAuthentication($strLDAPHost, $strLogonUserID, $strLogonUserPassword){
        $iBindReturnCode = null;
        // Validate password is not null, otherwise directory servers implementing unauthenticated bind (https://tools.ietf.org/html/rfc4513#section-5.1.2) will return 0 on auth attempts with null password
        if( strlen($strLogonUserPassword) < 1){
            $iBindReturnCode = -1;
        }
        else{
            $userDS = ldap_connect($strLDAPHost);
            if($userDS){
                ldap_set_option($userDS, LDAP_OPT_PROTOCOL_VERSION, 3);

                $userBind = ldap_bind($userDS, $strLogonUserID . '@example.com', $strLogonUserPassword);
                $iBindReturnCode = ldap_errno($userDS);
                ldap_close($userDS);
            }
            // ldap connection failed
            else{
                $iBindReturnCode = -2;              
            }        
        }
        return $iBindReturnCode;
    }

    $iBadUser = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "xe0012345", 'N0tTh3P@s5w0rd');
    print "\nInvalid user: $iBadUser\n";

    $iUserAuthenticated = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "e012345", 'Go0dP@s5w0rdH3r3');
    print "\nGood password: $iUserAuthenticated\n";

    $iBadPassword = activeDirectoryLDAPAuthentication("ldaps://ad.example.com", "e0012345", 'N0tTh3P@s5w0rd');
    print "\nBad password: $iBadPassword\n";

    $iBadHost = activeDirectoryLDAPAuthentication("ldaps://abc.example.com", "e0012345", 'N0tTh3P@s5w0rd');
    print "\nBad host: $iBadHost\n";

?>