Month: August 2018

Rocketbeans Lamp – Update #1

Rocketbeans Lamp – Update #1

Hey guys,
recently Rocketbeans updated their Website and also updated the API Endpoint for our beautiful lamp.

If you haven’t read about the Smart RGB-LED Lamp before you better take a look at my older Post here.

Changes

  • The API endpoint changed to https://api.rocketbeans.tv/v1/frontend/init
  • Added all possible colors (Cyan, Yellow and Purple)
  • Added error signals
    • Cyan = If JSON parsing failed
    • Purple = If show type not matching [“rerun”, “preview”, “live”]
    • Yellow = If HTTP connection failed
  • Added a switch to enable or disable error signals
  • I needed to dig deeper into the JSON to get the current show type

Code

As usual you are able to watch or star my Github repository to keep updated.

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

/*
* Coniguration
** CONNECTION_INDICATOR - blinking led during startup when defined
** ERROR_INDICATOR - blinking led sequence when parsing or http error occured
*/
#define CONNECTION_INDICATOR
#define ERROR_INDICATOR
#define LED_RED GPIO_NUM_14
#define LED_GREEN GPIO_NUM_27
#define LED_BLUE GPIO_NUM_26

#define WIFI_SSID "Your Wifi"
#define WIFI_PASSWORD "Your Wifi Password"
#define ROCKETBEANS_API "https://api.rocketbeans.tv/v1/frontend/init"

/*
* Information
*/
#define __product__ "Beanlight"
#define __version__ "1001DEV"
#define __author__ "Marvyn Zalewski <[email protected]>"
#define __copyright__ "(c) 2018 KeyboardInterrupt.org"

/*
* Global Variables
*/
int status = WL_IDLE_STATUS;

/*
* Functions
*/
void setLampToBlack()
{
    digitalWrite(LED_RED, LOW);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_BLUE, LOW);
}

void setLampToBlue()
{
    digitalWrite(LED_RED, LOW);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_BLUE, HIGH);
}

void setLampToGreen()
{
    digitalWrite(LED_RED, LOW);
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, LOW);
}

void setLampToCyan()
{
    digitalWrite(LED_RED, LOW);
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, HIGH);
}

void setLampToRed()
{
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_BLUE, LOW);
}

void setLampToPurple()
{
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_GREEN, LOW);
    digitalWrite(LED_BLUE, HIGH);
}

void setLampToYellow()
{
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, LOW);
}

void setLampToWhite()
{
    digitalWrite(LED_RED, HIGH);
    digitalWrite(LED_GREEN, HIGH);
    digitalWrite(LED_BLUE, HIGH);
}

/*
* Init
*/
void setup()
{
    Serial.begin(115200);
    Serial.println(__product__);
    Serial.println(__version__);
    Serial.println(__author__);
    Serial.println((String)__copyright__ + "\n");
    pinMode(LED_RED, OUTPUT);
    pinMode(LED_BLUE, OUTPUT);
    pinMode(LED_GREEN, OUTPUT);
    while (status != WL_CONNECTED)
    {
        Serial.println((String)__product__ + " attempting to connect to Wifi network, WIFI_SSID: " + (String)WIFI_SSID);
        status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
        #ifdef CONNECTION_INDICATOR
        setLampToRed();
        delay(2000);
        setLampToBlue();
        delay(2000);
        setLampToGreen();
        delay(2000);
        #else
        delay(6000);
        #endif
    };
    #ifdef CONNECTION_INDICATOR
    for (int i = 0; i <= 3; i++)
    {
        setLampToGreen();
        delay(250);
        setLampToBlack();
        delay(250);
    };
    #endif
    Serial.println((String)__product__ + " connected to network");
}

void loop()
{
    if ((WiFi.status() == WL_CONNECTED))
    {
        HTTPClient http;
        http.begin(ROCKETBEANS_API);
        int httpCode = http.GET();
        if (httpCode > 0)
        {
            const size_t bufferSize = JSON_ARRAY_SIZE(4) + JSON_ARRAY_SIZE(8) + 2 * JSON_OBJECT_SIZE(2) + 10 * JSON_OBJECT_SIZE(3) + 5 * JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(8) + 1190;
            DynamicJsonBuffer jsonBuffer(bufferSize);
            String payload = http.getString();
            JsonObject &root = jsonBuffer.parseObject(payload);
            JsonObject &data = root["data"];
            JsonObject &data_streamInfo = data["streamInfo"];
            JsonObject &data_streamInfo_info = data_streamInfo["info"];
            String data_streamInfo_info_type = data_streamInfo_info["type"].as<String>();

            if (!root.success())
            {
                Serial.println((String)__product__ + " parsing failed.");
                Serial.println(payload);
                #ifdef ERROR_INDICATOR
                for (int i = 0; i <= 6; i++)
                {
                    for (int i = 0; i <= 3; i++)
                    {
                        setLampToCyan();
                        delay(100);
                        setLampToBlack();
                        delay(100);
                    };
                    delay(500);
                }
                #endif
                setLampToCyan();
            } else
            {
                /* When current show is live */
                if (data_streamInfo_info_type == "live")
                    setLampToRed();
                /* When current show is new */
                else if (data_streamInfo_info_type == "premiere")
                    setLampToBlue();
                /* When current show is playback */
                else if (data_streamInfo_info_type == "rerun")
                    setLampToWhite();
                /* Graceful handling if no type matched */
                else {
                    Serial.println((String)__product__ + " unsupported show type -> "+ data_streamInfo_info_type +".");
                    #ifdef ERROR_INDICATOR
                    for (int i = 0; i <= 3; i++)
                    {
                        for (int i = 0; i <= 3; i++)
                        {
                            setLampToPurple();
                            delay(100);
                            setLampToBlack();
                            delay(100);
                        };
                        delay(500);
                    }
                    #endif
                    setLampToPurple();
                }
            }
        }
        else
        {
            Serial.println((String)__product__ + " error on HTTP request");
            #ifdef ERROR_INDICATOR
            for (int i = 0; i <= 3; i++)
            {
                for (int i = 0; i <= 3; i++)
                {
                    setLampToRed();
                    delay(100);
                    setLampToBlack();
                    delay(100);
                    setLampToYellow();
                    delay(100);
                    setLampToBlack();
                    delay(100);
                };
                delay(500);
            }
            #endif
            setLampToYellow();
        }
        http.end();
    }
    delay(60000);
}
Stop Entering Your SSH Passphrase All The Time

Stop Entering Your SSH Passphrase All The Time

You use SSH every day. You check out Git repositories, connect to servers and you Rsync a backup of your priceless home directory to remote machines for archiving and syncing. Because you know the importance of security you never use password authentication but an at least 4096 bit long RSA public-private keypair, encrypted by a passphrase only you know.

You are great. But you also waste time and patience, because you always enter the same single passphrase every time your computer wants to use SSH. Is it more secure, if you need to enter it every time? Probably not. The chance of someone figuring out (or keylogging) your passphrase is high enough, but I also heard of people who keep their passphrase in the clipboard for fast entry. Wow.

There is a simple, secure and elegant way to save your passphrase in-memory for your whole working session. Authentication agents like Pageant (Windows) or ssh-agent (OSX / Linux) can safely store your password and provide it to the SSH application when it requests a passphrase for your key.

„No Talk. Just Solution.“

Here is how I use it on my Linux and OSX machines. It requires you to install something called an SSH Agent Frontend – so basically a software that in turn talks to the ssh-agent – but in turn it provides a very elegant solution that manages the ssh agent, gpg agents and works even outside of environment scope (for cron jobs, etc.).

Using the frontend is optional and you can use the plain ssh-agent if you make sure to check for, inherit and run ssh-agent processes when needed. I strongly recommend using Keychain, though.

Installation & Configuration for Linux, OSX with Bash

I assume you already have installed SSH together with an SSH Agent, which is the case on most systems. I also assume that you use the bash or can transfer this article to other shells of your choice.

  • Install keychain by Funtoobrew install keychain yum install keychain apt-get install keychain pacman -Syu keychain (Or download and install manually from funtoo.org/Keychain)
  • Edit your ~/.bashrc and append the following line:eval `keychain --agents ssh --eval id_rsa` (Where ssh is the agent you want to use and id_rsa is a list of paths to your private key(s))
  • Re-open your shell or terminal emulator.

Keychain will ask you to enter your passphrase once and save it to the ssh-agent. You no longer need to enter your passphrase

Use keychain --stop all to stop all agents. The next time you start bash or your terminal emulator (and effectively keychain) you will be asked to unlock your private key with your passphrase again.

Installation & Configuration for Windows

Because Windows has no stable built-in SSH client we use an open-source tool called PuTTY. It comes bundled with an authentication agent called Pageantwhich works the same way as the OpenSSH SSH agent for Linux and OSX.

  • Install PuTTY by Simon Tathamchoco install putty (Or download and install manually from putty.org)
  • Open it and select Add... to load and unlock your private keys with your passphrases.
  • Use PuTTY like you would normally. It automatically detects the running Pageant authentication agent.

See Also

iPXE Boot With Shoelaces

iPXE Boot With Shoelaces

Hey guys,
today I’d like to tell you about Shoelaces from Thousandeyes. Shoelaces may be used to automate Server installation with iPXE Boot.
If you are already familiar with the basics about PXE, iPXE and how to set up the environment you can go ahead to Shoelaces Setup.

Environment

Before I dig into the details I prepared an Image to show you the Software Stack and also the whole server set up.

Software Stack and Server Setup

All commands are executed on a server named pxe-server.local with IP 192.168.0.2 running on CentOS 7.4.

DHCPD (ISC DHCP)

The DHCP Server is the first line of the stack. As soon as the Client boot in PXE Mode, it requests an IP Address and receives an IP address with the command where it can find the Image to boot from.
I prepared an example configuration file below.

[email protected]:[~]: cat /etc/dhcp/dhcpd.conf
subnet 192.168.0.0 netmask 255.255.255.0 {
  range 192.168.0.10 192.168.0.20;
  option subnet-mask 255.255.255.0;
  option routers 192.168.0.1;
  option domain-name-servers 192.168.0.1;
  next-server 192.168.0.2;
  if exists user-class and option user-class = "iPXE" {
    filename "http://192.168.0.2/poll/1/${netX/mac:hexhyp}";
  } else {
    filename "undionly.kpxe";
  }
}

As you can see the DHCP Server manages the IP Range 192.168.0.10-20 and also sends the next-server option which tells the client to boot from 192.168.0.2:69/filename (TFTP Server) option. If the Client supports iPXE it boots from http://192.168.0.2/poll/1/${netX/mac:hexhyp} which is Shoelaces. If the client doesn’t support iPXE it boots a microkernel which supports iPXE.

You can test your DHCP Settings with nmap


[email protected]:[~]: nmap --script broadcast-dhcp-discover
 
Starting Nmap 6.40 ( http://nmap.org ) at 2018-08-06 12:58 UTC
Pre-scan script results:
| broadcast-dhcp-discover:
|   IP Offered: 192.168.0.10
|   DHCP Message Type: DHCPOFFER
|   Server Identifier: 192.168.0.2
|   IP Address Lease Time: 0 days, 0:05:00
|   Subnet Mask: 255.255.255.0
|   Router: 192.168.0.1
|_  Domain Name Server: 192.168.0.1
WARNING: No targets were specified, so 0 hosts scanned.
Nmap done: 0 IP addresses (0 hosts up) scanned in 0.07 seconds

TFTP

The TFTP Server is needed to serve the undionly.kpxe when the client doesn’t support iPXE.

To “install” the microkernel you need to download the file from ipxe.org and save it into /var/lib/tftpboot.

[email protected]:[~]: wget -P /var/lib/tftpboot https://boot.ipxe.org/undionly.kpxe

Xinetd

You need Xinetd to actually serve the microkernel.

Here is the config file we used to glue tftp and Xinetd together:

[email protected]:[~]: cat /etc/xinetd.d/tftp
{
    socket_type     = dgram
    protocol        = udp
    wait            = yes
    user            = root
    server          = /usr/sbin/in.tftpd
    server_args     = -s /var/lib/tftpboot
    disable         = no
    per_source      = 11
    cps             = 100 2
    flags           = IPv4
}

As soon as you created the config file you need to restart xinetd and check if the tftp module is running.

[email protected]:[~]: systemctl status xinetd.service
● xinetd.service - Xinetd A Powerful Replacement For Inetd
   Loaded: loaded (/usr/lib/systemd/system/xinetd.service; enabled; vendor preset: enabled)
   Active: active (running) since Mo 2018-08-11 15:19:41 UTC; 3s ago
  Process: 8060 ExecStart=/usr/sbin/xinetd -stayalive -pidfile /var/run/xinetd.pid $EXTRAOPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 8061 (xinetd)
   CGroup: /system.slice/xinetd.service
           └─8061 /usr/sbin/xinetd -stayalive -pidfile /var/run/xinetd.pid
 
Aug 11 15:19:41 pxe-server xinetd[8061]: removing daytime
Aug 11 15:19:41 pxe-server xinetd[8061]: removing discard
Aug 11 15:19:41 pxe-server xinetd[8061]: removing discard
Aug 11 15:19:41 pxe-server xinetd[8061]: removing echo
Aug 11 15:19:41 pxe-server xinetd[8061]: removing echo
Aug 11 15:19:41 pxe-server xinetd[8061]: removing tcpmux
Aug 11 15:19:41 pxe-server xinetd[8061]: removing time
Aug 11 15:19:41 pxe-server xinetd[8061]: removing time
Aug 11 15:19:41 pxe-server xinetd[8061]: xinetd Version 2.3.15 started with libwrap loadavg labeled-networking options compiled in.
Aug 11 15:19:41 pxe-server xinetd[8061]: Started working: 1 available service
[email protected]:[~]: netstat -tulpen | grep xinetd
udp        0      0 0.0.0.0:69              0.0.0.0:*                           0          235681     8061/xinetd

When you execute the status command at the end of the log should be at least ‘1’ available service printed. The Port 69/UDP should be open as well.

Shoelaces

Now we’re running the basic iPXE Environment. At the moment we are able to boot a client with PXE boot, obtain an IP Address and boot the iPXE microkernel if the client doesn’t support iPXE.

But what happens if the client boot with this line (which we configured in the dhcpd section)?
http://192.168.0.2/poll/1/${netX/mac:hexhyp}
Before answering the question I’ll guide you through the shoelaces installation.

Installation

Shoelaces is a program written in Go Lang. Therefore we need to install go at first.

# Determine your needed go version at https://golang.org/dl/#stable first. I need the amd64 binary which is version 1.10.3 at the moment.
[email protected]:[~]: wget https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
[email protected]:[~]: tar -xvf go1.10.3.linux-amd64.tar.gz
[email protected]:[~]: mv go /usr/local
[email protected]:[~]: echo "export GOROOT=/usr/local/go" >> /etc/bashrc
[email protected]:[~]: echo "export GOPATH=/opt/go" >> /etc/bashrc
[email protected]:[~]: echo "export PATH=$GOPATH/bin:$GOROOT/bin:$PATH" >> /etc/bashrc
[email protected]:[~]: src /etc/bashrc

To install shoelaces you need to get the repository with go get then you need to build the shoelaces binary.

[email protected]:[~]: go get github.com/thousandeyes/shoelaces
[email protected]:[~]: cd $GOPATH/src/github.com/thousandeyes/shoelaces
[email protected]:[~]: go build

Daemonize

Currently, we’re only able to start the service on demand with a command.

[email protected]:[~]: cd $GOPATH/src/github.com/thousandeyes/shoelaces
[email protected]:[~]: ./shoelaces -config configs/shoelaces.conf

But if you want the process permanently running you need to create a systemd file.
Luckily I already created one which needs a user to be created.

[email protected]:[~]: useradd shoelaces
[email protected]:[~]: cat /lib/systemd/system/shoelaces.service
[Unit]
Description=Shoelaces
Documentation=https://github.com/thousandeyes/shoelaces
ConditionPathExists=/opt/go/src/github.com/thousandeyes/shoelaces
After=network.target

[Service]
User=shoelaces

# Restart Policy
Restart=on-failure
RestartSec=10

# Execution
ExecStartPre=/bin/chown -R shoelaces /opt/go/src/github.com/thousandeyes/shoelaces
ExecStartPre=/bin/chmod u+x /opt/go/src/github.com/thousandeyes/shoelaces/shoelaces
ExecStart=/opt/go/src/github.com/thousandeyes/shoelaces/shoelaces -config configs/shoelaces.conf

# Environment
WorkingDirectory=/opt/go/src/github.com/thousandeyes/shoelaces
PermissionsStartOnly=true
NonBlocking=true

# Logging
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=shoelaces

[Install]
WantedBy=multi-user.target

After you added the content to the file /lib/systemd/system/shoelaces.service execute systemctl daemon-reload; systemctl start shoelaces.service; systemctl status shoelaces.service to start the service and check the status. You can always take a look into logs if you execute journalctl -f -u shoelaces.service.

Configure

I guess you already noticed the only missing part. Configuring Shoelaces.

It’s straight forward and fairly easy. Just go into the shoelaces directory and take a look at the default config file.

[email protected]:[~]: cd $GOPATH/src/github.com/thousandeyes/shoelaces
[email protected]:[/opt/go/src/github.com/thousandeyes/shoelaces]: cat configs/shoelaces.conf
port=8081
domain=localhost
data-dir=configs/example-templates-configs/
template-extension=.slc
mappings-file=mappings.yaml
debug=true

For production I changed the config file to the following one …

[email protected]:[/opt/go/src/github.com/thousandeyes/shoelaces]: cat configs/shoelaces.conf
port=8080
domain=192.168.0.2
data-dir=configs/templates-configs/
template-extension=.slc
mappings-file=mappings.yaml
debug=true

… and moved configs/example-templates-configs to configs/templates-configs.

Starting

Recently we changed the config file. Therefore you need to restart your service.

Now open your shoelaces URL (http://<domain>:<port>) with your favourite browser. If everything went well you should see the following website. If not, take a look into the logs what’s wrong with shoelaces.

Shoelaces Startup Image

Deep Dive

Back to the question before the shoelaces installation.

But what happens if the client boot with this line (which we configured in the dhcpd section)?
http://192.168.0.2/poll/1/${netX/mac:hexhyp}

The client connects to shoelaces and kept in a waiting loop until the user executes a “Boot!” command or shoelaces is able to bootstrap the server with ‘mappings’. This state looks like this:

Server Registered

As soon as you click on ‘Select an iPXE script’ you are able to choose an iPXE script located in $GOPATH/src/github.com/thousandeyes/shoelaces/configs/templates-configs/ipxe.

I clicked on centos.ipxe and filled out both arguments. If you check $GOPATH/src/github.com/thousandeyes/shoelaces/configs/templates-configs/ipxe/centos.ipxe.slc there are two strings which look like {{.<STRING>}}. These strings are the arguments shown in the UI.

[email protected]:[~]: cat $GOPATH/src/github.com/thousandeyes/shoelaces/configs/example-templates-configs/ipxe/centos.ipxe.slc
{{define "centos.ipxe" -}}
#!ipxe
set hostname {{.hostname}}
set release {{.release}}
set base http://mirror.centos.org/centos/${release}/os/x86_64

echo This automatically overwrites data!
echo CentOS ${release}
echo Installing ${hostname}

kernel ${base}/images/pxeboot/vmlinuz initrd=initrd.img repo=${base} ks=http://{{.baseURL}}/configs/centos.ks?hostname=$hostname&release=$release
initrd ${base}/images/pxeboot/initrd.img
boot
{{end}}

As soon as I click on the big red button “Boot!” the client leaves the waiting loop and starts to execute the iPXE script. It depends on your kickstart file how long it takes until your client is back again but normally it should’nt take longer than 10 minutes.

Conclusion

Shoelaces is a very small footprint software written in go which supports bootstrapping a huge amount of server. Even in a small environment, it helps a lot. Take a look at their Github Project Site if you want to discover all further features.

For example you may use the mapping feature if you want to bootstrap server automatically based on IP or Hostname pattern.

Leave a comment or contribute to the project if you like the software.