Was ist Consul und wozu dient es?

Eine Beschreibung in einem Wort ist hier Service Mesh. Was jemanden, der sich zuerst mit Consul beschäftigt, ebenfalls nicht wirklich weiterhilft. Dieser Blogpost zeigt die Features von Consul auf und gibt Hinweise zu deren Anwendung.

Consul verwaltet Services und Knoten in der Art eines “Verzeichnisdienstes”, also in der Form was läuft wo? Zugegriffen wird dabei über DNS oder alternativ mittels HTTP(s). Consul wird entweder im Modus Server oder Agent betrieben. Die Server speichern die Daten, kommen mehrere zum Einsatz werden die Daten automatisch synchronisiert.

Auf die Daten kann man direkt auf den Servern mittels DNS oder HTTP(s) zugreifen oder über den eh benötigten Agenten. Über den Agenten meldet sich jeder beliebige Knoten bzw. Host in Consul an und wird als Node registriert. Ebenfalls können dann auch Services registriert werden. Ein Webserver z.B. registriert sich so zum Beispiel als neues Cluster-Mitglied zu einem bereits bestehenden Services. Zusätzlich können auch Tags gesetzt werden, die sich via DNS-Abfrage dann als Aliases darstellen.

Nur was macht man nun mit diesen Informationen? Einfach wäre es nun seinen eigentlichen DNS auf diese Informationen weiterzuleiten. Dies geht wie oben schon angedeutet mit Anfragen entweder direkt an die Server oder über einen auf den DNS-Servern betriebenen Agenten. Damit ist leicht ein DNS-Round-Robin zu realisieren.

Möchte man hingegen einen Load-Balancer als Proxy für seinen Dienst betreiben, kommt Consul-Templates als eigenständiges Produkt ins Spiel. Es handelt sich hierbei um eine Template-Engine, die die Informationen aus Consul in Konfigurationsdateien überführt. Die Konfiguration kann dabei noch um zusätzliche Konfigurationsparamter, aus dem in Consul integrierten Key-Value-Store, angereichert werden.

Abschließend bietet Consul als weiteres Feature die Verwaltung einer Certificate Authority (CA). Die ausgestellten Zertifikate können sogleich mit Hilfe des Agents via Proxy (sidecar proxy) zur Absicherung der betriebenen Dienste mittels TLS eingesetzt werden. Das geschieht völlig transparent und Konfigurationsdateien müssen nicht angepasst werden.

Lennart Betz
Lennart Betz
Senior Consultant

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.
A journey with Vault – Teil 1

A journey with Vault – Teil 1

This entry is part of 1 in the series a journey with vault

Hello fellow blog readers!

Heute möchte ich euch auf die Reise mit Vault by Hashicorp mitnehmen.

Zunächst was ist Vault? Bei Vault handelt es sich stumpf gesagt, um einen Passwortspeicher. Vermutlich kommen da jetzt bei dem einen oder anderen Projekte wie Keypass oder Enpass in den Sinn. Die Richtung ist schon mal gut. Jedoch kennt auch jeder das Hauptproblem der oben genannten Lösungen nur zu gut.

Teamfähigkeit.

Das eine Projekt beherrscht es, andere nur teilweise oder vieleicht sogar garnicht. Frustrierend könnte man die Situation auch gerne umschreiben. Fakt ist auf jeden Fall das es sich bei Vault um eine Lösung handelt, die wirklich das Zeug dazu hat ein Teamfähiger Passwortspeicher zu sein. Wie so alles in der Welt haben Dinge leider ihren Preis. Man wird mit Teamfähigkeit gesegnet, aber Satan bestraft uns indirekt mit der Komplexität des ganzen Konstrukts, weswegen ich das Abenteuer Vault in eine kleine Serie verpacke. Genug Worte für den Einstieg, legen wir los mit dem neuen Abenteuer in der Hautprolle: Vault.

Part Uno widment sich der grundlegenden Inbetriebnahme von Vault.

Wie immer benutzte ich eine mit Vagrant provisionierte höchst aktuelle CentOS 7 Box der Marke Eigenbau mit VirtualBox als Provider.

Die Reise beginnt mit dem Download eines ZIP-Archivs welche das Vault binary enthält. Den legen wir einfach unter /tmp ab und entpacken ihn direkt nach /usr/local/bin

wget https://releases.hashicorp.com/vault/1.3.0/vault_1.3.0_linux_amd64.zip -P /tmp
unzip /tmp/vault_1.3.0_linux_amd64.zip -d /usr/local/bin
chown root. /usr/local/bin/vault

Damit das aufrufen von Vault direkt gelingt müssen wir unsere PATH Variable noch um /usr/local/bin ergänzen. Ich hab das ganze in meinem ~/.bash_profile hinterlegt:

PATH=$PATH:$HOME/bin:/usr/local/bin

Wenn alles korrekt ausgeführt wurde, können wir jetzt die Autocompletion nachziehen und anschließend die Shell neustarten:

vault -autocomplete-install
complete -C /usr/local/bin/vault vault
exec $SHELL

Um das ganze abzurunden lassen wir Vault als Daemon laufen.

Zunächst müssen wir es Vault gestatten mlock syscalls ohne der Notwendigkeit von root ausführen zu dürfen:

setcap cap_ipc_lock=+ep /usr/local/bin/vault

Danach legen wir einen nicht priviligierten Systembenutzer an, unter dem der Vault Daemon später laufen soll:

useradd --system --home /etc/vault.d --shell /bin/false vault

Jetzt kommt die systemd Unit:

touch /etc/systemd/system/vault.service

… mit folgenden Inhalt:

[Unit]
Description="HashiCorp Vault - A tool for managing secrets"
Documentation=https://www.vaultproject.io/docs/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/vault.d/vault.hcl
StartLimitIntervalSec=60
StartLimitBurst=3

[Service]
User=vault
Group=vault
ProtectSystem=full
ProtectHome=read-only
PrivateTmp=yes
PrivateDevices=yes
SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK
Capabilities=CAP_IPC_LOCK+ep
CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK
NoNewPrivileges=yes
ExecStart=/usr/local/bin/vault server -config=/etc/vault.d/vault.hcl
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=60
StartLimitIntervalSec=60
StartLimitBurst=3
LimitNOFILE=65536
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target

Bevor wir den Daemon starten können, müssen wir ein paar Verzeichnisse sowie eine Konfigurationsdatei anlegen:
mkdir -pv /etc/vault.d/
mkdir -pv /usr/local/share/vault/data/
chown -R vault. /usr/local/share/vault/

touch /etc/vault.d/vault.hcl

Meine Konfigurationsdatei ist als Beispielhaft anzusehen. Sie behinhaltet das nötigste um den Vault Server grundsätzlich starten zu können. Diese sollte entsprechend an das eigene Szenario angepasst werden und unbedingt mit Zertifikaten ausgestattet sein!

storage "file" {
path = "/usr/local/share/vault/data"
}

ui = true

listener "tcp" {
address = "172.28.128.25:8200"
tls_disable = "true"
}

api_addr = "http://172.28.128.25:8200"
cluster_addr = "http://172.28.128.25:8201"

systemd neuladen und den Vault Daemon starten:

systemctl daemon-reload
systemctl start vault

Wenn uns alles geglückt ist, sollten wir unter der Adresse des Servers, mit Angabe des Ports 8200 nun die UI von Vault vorfinden. Damit geht es nun in der Bildstrecke weiter:

Das wars für den ersten Teil der Serie, im zweiten Teil werden wir uns den Aufbau von Vault genauer anschauen und uns der integration von SSH widment. Vault bietet nämlich viele Integrationsmöglichkeiten mit denen sich am Ende die Authentifizierung von sämtlichen Dienste Zentralisiert über Vault steuern lassen. Bis dahin wünsche ich wie immer viel Spass beim Basteln!

Photo from: https://devopscube.com/setup-hashicorp-vault-beginners-guide/

Max Deparade
Max Deparade
Consultant

Max ist seit Januar als Consultant bei NETWAYS und unterstützt tatkräftig unser Professional Services Team. Zuvor hat er seine Ausbildung zum Fachinformatiker für Systemintegration bei der Stadtverwaltung in Regensburg erfolgreich absolviert. Danach hat der gebürtige Schwabe, der einen Teil seiner Zeit auch in der Oberpfalz aufgewachsen ist ein halbes Jahr bei einem Managed Hosting Provider in Regensburg gearbeitet, ehe es ihn zu NETWAYS verschlagen hat. In seiner Freizeit genießt Max vor allem die Ruhe, wenn...

SSH – Der Schlüssel zum Erfolg

Seal of ApprovalOder wie man das meiste aus SSH herauskitzelt.

SSH wird von den meisten Sysadmins genutzt, aber selten so wie es genutzt werden sollte.
Die meisten Administratoren verwenden das folgende Kommando Tag ein Tag aus.

ssh-keygen -t rsa -b 4096 -C "dummy_user@fqdn_dummy.com"

Zerlegen wir das Kommando mal in seine Einzelteile und versuchen zu verstehen was diese eigentlich tun.

Der Parameter '-t' gibt an welchen Key-Typ wir rsa/dsa/ecdsa/ecdsa-sk/ed25519 erstellen wollen. Hierbei wird von kleineren RSA-Keys unter 4096 Bits inzwischen abgeraten. DSA ist als unsicher deklariert und sollte auch seit Version 7 von OpenSSH auch nicht verwendet werden. ECDSA ist wegen der NSA ggf. problematisch zum nachlesen hier. So bleibt nur ED25519 welchen ich bei meiner täglichen Arbeit kaum sehe. Dies sollte aber heutzutage der De-Facto-Standard-Key sein, mit dem das obige Kommando aufgerufen wird.

'-b' für die Bits welche verwendet werden sollen. Wenn nun an dieser Stelle die Frage aufkommt mit welcher Bits-Anzahl ED25519 erstellt werden sollte, sei gesagt dass dieser eine festgelegte Länge hat und damit diesen Parameter ignoriert.

'-C' steht für Comment und dieser kann beliebig sein. Zumeist wird hier aber zur besseren Nachvollziehbarkeit von den Admins dieser Welt das Pattern Username@Hostname verwendet, damit man sehen kann für wen dieser Key erstellt wurde.

Im obigen Beispiel fehlt der Parameter '-N' welcher für new_passphrase bzw. das Passwort steht, wenn man den fehlenden Parameter '-N' mit "" ohne Zeichenkette angibt erhält man einen passwortlosen, privaten SSH-Schlüssel. Zusätzlich wird ein öffentlicher Schlüssel erstellt.

Verbessern wir das obige Kommando: ssh-keygen -t ed25519 -N'' -C 'dummy_user@fqdn_dummy.com'

Der Output des Beispiels:

ssh-keygen -t ed25519 -N'' -C'dummy_user@fqdn_dummy.com'
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519): /root/.ssh/dummy
Created directory '/root/.ssh'.
Your identification has been saved in /root/.ssh/dummy.
Your public key has been saved in /root/.ssh/dummy.pub.
The key fingerprint is:
SHA256:14RQXbFyQCtEZfnGN8O7hV4h9hAEY0SYQN9Y0+9TRp8
root@fqdn_dummy.com The key's randomart image is:
+--[ED25519 256]--+
|      .oooO%O+o. |
|        .==o== ..|
|         oo.+o*.o|
|           + *+E=|
|        S . o.=+*|
|         .    .=o|
|             . .+|
|              .. |
|                 |
+----[SHA256]-----+
[root@fqdn_dummy.com .ssh]# ll
total 8
-rw-------. 1 root root 464 Nov 14 22:45 dummy
-rw-r--r--. 1 root root 108 Nov 14 22:45 dummy.pub

Damit hätten wir einen Standard-SSH-Schlüsselpaar, welches zumeist in den Unternehmen vergessen wird, wenn Mitarbeiter das Unternehmen verlassen oder die Abteilungen wechseln und auch durch automatisierte Deployments wird es oft als Karteileiche liegen gelassen.

Wie wäre es wenn wir ablaufende SSH-Schlüsselpaare hätten, welche nach einer gewissen Zeit ablaufen und zwar von selbst?

Was vielen nicht bewusst ist, ist dass dies SSH eigentlich schon von ganz allein kann, nur nutzt es kaum jemand.

Beginnen wir mit dem ersten Schritt und generieren uns eine CA (Certificate Authority).

ssh-keygen -t ed25519 -N '' -C 'ca' -f ca

Da diese CA unser Dreh- und Angelpunkt ist bedarf es Achtsamkeit, damit diese nie in falsche Hände gelangt.

Dank dieser CA können wir nun Zertifikate von Hosts und Benutzern signieren.

Als Beispiel nehmen wir einen Benutzer 'test' welcher sich per SSH an den Host namens ColdMoon anmelden soll.

Wir brauchen also für beide Zertifikate und einen SSH Schlüssel.

Bevor wir es vergessen müssen wir unsere vorher erstellte CA auf unserem SSH Server in der sshd_config als 'TrustedUserCAKeys /etc/ssh/ca.pub' eintragen.

Nun nehmen wir von unserem Benutzer 'test' auf seinem Laptop und den dort bereits erstellten öffentlichen Schlüssel mit dem wir ihm begrenzten Zugriff auf unsere Infrastruktur geben wollen in diesem Fall unser Rechner ColdMoon.

Im Vorfeld haben wir schon den Benutzer auf dem Rechner angelegt, und der Kollege 'test' hat uns seinen öffentlichen Schlüssel per Email geschickt.
Von unserem Vorgesetzten wissen wir das der Kollege 'test' nur 1 Woche ein Praktikum bei uns vornimmt und danach das Unternehmen wieder verlässt.

Auf gehts wir erstellen einen zeitlich begrenzten Zugang:

ssh-keygen -s ca -I Azubi_auf_Zeit -n test -V +1w -z 1 test.pub
Signed user key test-cert.pub: id "Azubi_auf_Zeit" serial 1 for test valid from 2019-11-13T09:00:00 to 2019-11-20T09:00:00

Das Resultat dieses Kommandos ist das wir ein signiertes 'test-cert.pub' erhalten mit eine Lebenszeit von 1 Woche.
Dieses senden wir umgehend unseren Praktikanten zu mit dem Hinweis das er sich mit dem folgenden Kommando nun an dem Rechner ColdMoon anmelden kann.

ssh -i ~/.ssh/test -i ~/.ssh/test-cert.pub test@ColdMoon -v

Ich habe mal den Verbose-Schalter genommen um zu zeigen wie das Zertifikat sich dann gegen ColdMoon anmeldet.

...
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<rsa-sha2-256,rsa-sha2-512>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: publickey
debug1: Offering public key: test ED25519 SHA256:4A3ab2/7dq0klERi9IevmSnTZd7dkOuuP8yrWCZ24gI explicit
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Offering public key: test ED25519-CERT SHA256:4A3ab2/7dq0klERi9IevmSnTZd7dkOuuP8yrWCZ24gI explicit
debug1: Server accepts key: test ED25519-CERT SHA256:4A3ab2/7dq0klERi9IevmSnTZd7dkOuuP8yrWCZ24gI explicit
debug1: Authentication succeeded (publickey).
Authenticated to 192.168.242.67 ([192.168.242.67]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Requesting authentication agent forwarding.
debug1: Sending environment.
debug1: Sending env LC_CTYPE = UTF-8
Last login: Thu Nov 14 11:00:14 2019 from 192.168.242.225
[test@coldmoon ~]$

Die Authentifizierung wird nun die gesamte Woche lang funktionieren in der der Praktikant Zugriff auf dem Rechner braucht, danach weil es nur zeitlich begrenzt ist nicht mehr. Damit ist für den Praktikanten kein Zugriff auf unserer Infrastruktur mehr möglich. Dies funktioniert auch mit anderen Hosts oder Services, welchen man nur für einen Zeitraum einen begrenzten Zugriff per SSH geben möchte.

Der Parameter '-V' gibt den Validity_Interval an, also den Zeitraum der Gültigkeit, und der Parameter '-z' eine Serialnumber, welche von uns frei gewählt werden kann.

Dies kann mit den üblichen Systemmitteln automatisiert werden oder per Ansible/Puppet auch auf Systeme verteilt werden.
Schöner ist es noch wenn es ohne viel manuelle Arbeit geht. Dies zeige ich in meinem nächsten Blogpost welcher Vault aus dem Hause Hashicorp zeigt.

Gruss

David

David Okon
David Okon
Support Engineer

Weltenbummler David hat aus Berlin fast den direkten Weg zu uns nach Nürnberg genommen. Bevor er hier anheuerte, gab es einen kleinen Schlenker nach Irland, England, Frankreich und in die Niederlande. Alles nur, damit er sein Know How als IHK Geprüfter DOSenöffner so sehr vertiefen konnte, dass er vom Apple Consultant den Sprung in unser Professional Services-Team wagen konnte. Er ist stolzer Papa eines Sohnemanns und bei uns mit der Mission unterwegs, unsere Kunden zu...

Wie bekomme ich bessere Informationen über Züge als die DB

marudor.de screenshotAls reisender Consultant mit Bahncard 100 verbringe ich sehr viel Zeit in Zügen der Deutschen Bahn. Das ist auf der einen Seite erfreulich, denn ich verbringe die Zeit nicht auf der Autobahn. Auf der anderen Seite macht es die Bahn einem manchmal schwer, sinnvoll von A nach B zu kommen. Es soll ja schon einmal vorgekommen sein, dass Züge ausfallen oder zu spät kommen. Möglicherweise ist auch mal ein Zug überfüllt, Klima defekt, Bier alle oder sonst etwas.

Die DB selber bietet für so etwas ja seit einiger Zeit den DB Navigator. Die wichtigsten Daten bekommt man hier auch. Allerdings weiß die Bahn eigentlich über ihre Züge viel mehr als sie uns sagt. Dieses Problem löst sehr gut und schon seit einiger Zeit die Seite marudor.de

Man sieht auf der Seite, wenn man nach einem Bahnhof, einem Zug oder einer Route sucht, ein gute Übersicht über den gewünschten Zug. Hier sieht man auf einen Blick:

  • Reihenfolge der Waggons mit EXAKTER Positon am Bahnsteig
  • ICE Baureihe inkl. Revision (dadurch weiß man auch, ob man alte bequeme oder neue unbequeme Sitze bekommt)
  • Die Postition der Comfortsitze im Zug, so dass man direkt da einsteigen kann (roter Punkt)
  • Die Position von Bordrestaurant, Kinderabteil, Behindertenbereich und -toilette
  • Ruhe und Handy-abteil
  • Wifi Accespoints (und ob er funktioniert)
  • Aktuelle UND vergangene Störungen auf der Fahrt

Teilweise bekommt man diese Infos auch woanders, allerdings sind sie bei marudor meistens aktueller und vor allem alles in einem Rutsch. Und nicht zuletzt: Verspätungen und Verspätungsprognosen sind besser und zuverlässiger.

Warum das alles so ist und wieso der marudor das alles macht kann man hier erfahren. Das Video kommt von der GPN 19 und ist sehr interessant anzuschauen.

Christoph Niemann
Christoph Niemann
Senior Consultant

Christoph hat bei uns im Bereich Managed Service begonnen und sich dort intensiv mit dem internen Monitoring auseinandergesetzt. Seit 2011 ist er nun im Consulting aktiv und unterstützt unsere Kunden vor Ort bei größeren Monitoring-Projekten und PERL-Developer-Hells.

OSMC 2019 – Day 2

OSMC Logo
The social event yesterday evening was a blast and the late lounge afterwards also a must. So while some were still recovering, the room for the first talk was already quite full. This showed the interest in Jochen Kressin‘s talk about “Zero Trusted Networks – why Perimeter Security is dead”. He explained the (old) assumption of perimeter security “I am behind a firewall, so my traffic is secure” and asked the question if this is still true. Showing examples proving it is not true anymore because if it would be, none of these data breaches would have been happen. He explained what has changed in the last years leading to “Zero Trusted Networks” where every system has to be treated as untrusted and how to adopt for it. As one of the developers he used Search Guard as example which adds security to Elasticsearch, one of the great tools that had no security by itself for a long time, being not ready for the zero trusted approach.
Zero Trusted NetworksFluentD
Toshaan Bravani was talking about “Monitoring your Logs with Fluent”. FluentD and the client component FluentBit is an alternative to Logstash I see more and more at customer environments, so I was happy to get a deeper look into it. In addition he showed the complete tool stack to get most out of your data and the automation used to get it up and running.

Open Source landscape for APM
Third one for today was “Improved Observability Using Automated, OpenCensus-based Application Monitoring Solutions” by Tobias Angerstein. He started with a nice overview of the Open Source landscape for Application Performance Management before focusing on inspectIT. Its latest incarnation inspectIT Ocelot focuses on Open Standards like Open Metrics, Open Tracing and Open Census which are forming a new one called Open Telemetry which allows integration with all the well-known tools like Telegraf, Prometheus, Grafana. It also provides End User Monitoring using Boomerang, a javascript agent, and an EUM Server which transforms data to the Open Standards. In his demo he showed the capability of it and my only thought was how helpful something like this would have been to me in my early IT days being a Java developer.

Afterwards we could enjoy another great lunch break and perhaps also an massage, before starting into the afternoon sessions.
Lunch breakLunch BreakLunch Break

Database observability
Charles Judith gave a talk about “How to improve database Observability”. In his job he is responsible for reliability of the company’s databases and told the crowd the problems he started with like having no backup and monitoring at all. So it was his personal goal to have no hidden issues anymore and get transparency into their environment. His way from zero to hero was quite interesting and he compared it with a roller coaster. In the end having metrics to tell users that they are right or wrong with their feeling of the database is slow and having logs and monitoring telling were the real problem lies instead of guessing has improved his daily work already. But he still has some more steps to do like publishing SLA. The WIP version of his toolkit can be found on Github.

Why BOFH is toxic
Second last one I attended was Jan Doberstein with a non technical talk about behaviour and how it influences your daily life and work, titled “Idiot! – or: Why BOFH is toxic”. He touched the same topic like the open discussion yesterday and I think it is great to get people think about and reflect their behaviour. While most of his examples were matched to the crowd and perhaps people working in IT do communicate much more in electronic fashion than others, it is a topic that everyone should care about.

High available setup
Last but not least Marcel Weinberg showed the high available setup he built for Digital Ocean. He included some very helpful small tips and tricks to increase performance and avoid pitfalls while diving deep into the configuration. Indeed it were too much for me to list them all here.

Pictures are taken again from the OSMC stream at Twitter, thanks to everyone for sharing their impressions. I hope everyone enjoyed the conference like I did. Thanks to everyone who made OSMC such a great experience again this year, starting with my colleagues organizing the event, the sponsors and speakers but this includes every attendee forming this nice community. Save travels for everyone leaving today or see you tomorrow if you join the Hackathon or Open Source Camp on Foreman. I hope I will see everyone next year at the same place on November 16th to 19th for OSMC 2020 or in Amsterdam for IcingaConf on May 12th to 14th.

Dirk Götz
Dirk Götz
Principal Consultant

Dirk ist Red Hat Spezialist und arbeitet bei NETWAYS im Bereich Consulting für Icinga, Puppet, Ansible, Foreman und andere Systems-Management-Lösungen. Früher war er bei einem Träger der gesetzlichen Rentenversicherung als Senior Administrator beschäftigt und auch für die Ausbildung der Azubis verantwortlich wie nun bei NETWAYS.

C++Go. Halb C++ – halb Go.

Scherz beiseite, die Go-Entwickler bieten noch keine Möglichkeit, C++-Bibliotheken ohne weiteres anzusprechen. Aber es geht ja auch mit weiteres. Das weitere besteht darin, dass C++-Funktionen mittels C-Bibliotheken gewrapped werden können. Und in meinem letzten Blogpost zu diesem Thema habe ich bereits erklärt, wie C-Bibliotheken in Go angesprochen werden können. Sprich, es braucht nur eine hauchdünne C-Schicht zwischen C++ und Go.

Multilingual++ in der Praxis

Wie auch letztes mal habe ich schon mal was vorbereitet – eine Schnittstelle für die Boost.Regex-Bibliothek. Diese findet sich in diesem GitHub-Repo und besteht aus folgenden Komponenten:

  • Ein Struct, das boost::basic_regex<char> wrapped
  • Eine C-Bibliothek, die den C++-Teil wrapped
  • Die Go-Bibliothek, die die C-Bibliothek verwendet

Wrapper-Struct

Dieses Struct ist Notwendig, da die Größe von boost::basic_regex<char> zwar C++ bekannt ist, aber nicht Go. Das Wrapper-Struct hingegen hat eine feste Größe (ein Zeiger).

libcxx/regex.hpp

#pragma once

#include <boost/regex.hpp>
// boost::basic_regex
// boost::match_results
// boost::regex_search

#include <utility>
// std::forward
// std::move

template<class Char>
struct Regex
{
	template<class... Args>
	inline
	Regex(Args&&... args) : Rgx(new boost::basic_regex<Char>(std::forward<Args>(args)...))
	{
	}

	Regex(const Regex& origin) : Rgx(new boost::basic_regex<Char>(*origin.Rgx))
	{
	}

	Regex& operator=(const Regex& origin)
	{
		Regex copy (origin);
		return *this = std::move(copy);
	}

	inline
	Regex(Regex&& origin) noexcept : Rgx(origin.Rgx)
	{
		origin.Rgx = nullptr;
	}

	inline
	Regex& operator=(Regex&& origin) noexcept
	{
		this->~Regex();
		new(this) Regex(std::move(origin));
		return *this;
	}

	inline
	~Regex()
	{
		delete this->Rgx;
	}

	template<class Iterator>
	bool MatchesSomewhere(Iterator first, Iterator last) const
	{
		boost::match_results<Iterator> m;
		return boost::regex_search(first, last, m, *(const boost::basic_regex<Char>*)this->Rgx);
	}

	boost::basic_regex<Char>* Rgx;
};

Eine C-Bibliothek, die den C++-Teil wrapped

Die folgenden Funktionen sind zwar waschechte C++-Funktionen, aber dank dem extern "C" werden sind sie in der Bibliothek als C-Funktionen sichtbar und können von Go angesprochen werden.

libcxx/regex.cpp

#include "regex.hpp"
// Regex

#include <boost/regex.hpp>
using boost::bad_expression;

#include <stdint.h>
// uint64_t

#include <utility>
using std::move;

extern "C" unsigned char CompileRegex(uint64_t pattern_start, uint64_t pattern_end, uint64_t out)
{
	try {
		*(Regex<char>*)out = Regex<char>((const char*)pattern_start, (const char*)pattern_end);
	} catch (const boost::bad_expression&) {
		return 2;
	} catch (...) {
		return 1;
	}

	return 0;
}

extern "C" void FreeRegex(uint64_t rgx)
{
	try {
		Regex<char> r (move(*(Regex<char>*)rgx));
	} catch (...) {
	}
}

extern "C" signed char MatchesSomewhere(uint64_t rgx, uint64_t subject_start, uint64_t subject_end)
{
	try {
		return ((const Regex<char>*)rgx)->MatchesSomewhere((const char*)subject_start, (const char*)subject_end);
	} catch (...) {
		return -1;
	}
}

libcxx/regex.h

#pragma once

#include <stdint.h>
// uint64_t

unsigned char CompileRegex(uint64_t pattern_start, uint64_t pattern_end, uint64_t out);

void FreeRegex(uint64_t rgx);

signed char MatchesSomewhere(uint64_t rgx, uint64_t subject_start, uint64_t subject_end);

Go-Bibliothek

Diese spricht letztendlich die C-Funktionen an. Dabei übergibt sie die Zeiger als Ganzzahlen, um gewisse Sicherheitsmaßnahmen von CGo zu umgehen. Das Regex-Struct entspricht dem Regex-Struct aus dem C++-Teil.

regex.go

package boostregex2go

import (
	"io"
	"reflect"
	"runtime"
	"unsafe"
)

/*
#include "libcxx/regex.h"
// CompileRegex
// FreeRegex
// MatchesSomewhere
*/
import "C"

type OOM struct {
}

var _ error = OOM{}

func (OOM) Error() string {
	return "out of memory"
}

type BadPattern struct {
}

var _ error = BadPattern{}

func (BadPattern) Error() string {
	return "bad pattern"
}

type Regex struct {
	rgx unsafe.Pointer
}

var _ io.Closer = (*Regex)(nil)

func (r *Regex) Close() error {
	C.FreeRegex(rgxPtr64(r))
	return nil
}

func (r *Regex) MatchesSomewhere(subject []byte) (bool, error) {
	defer runtime.KeepAlive(subject)
	start, end := bytesToCharRange(subject)

	switch C.MatchesSomewhere(rgxPtr64(r), start, end) {
	case 0:
		return false, nil
	case 1:
		return true, nil
	default:
		return false, OOM{}
	}
}

func NewRegex(pattern []byte) (*Regex, error) {
	rgx := &Regex{}

	defer runtime.KeepAlive(pattern)
	start, end := bytesToCharRange(pattern)

	switch C.CompileRegex(start, end, rgxPtr64(rgx)) {
	case 0:
		return rgx, nil
	case 2:
		return nil, BadPattern{}
	default:
		return nil, OOM{}
	}
}

func bytesToCharRange(b []byte) (C.uint64_t, C.uint64_t) {
	sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	return C.uint64_t(sh.Data), C.uint64_t(sh.Data + uintptr(sh.Len))
}

func rgxPtr64(p *Regex) C.uint64_t {
	return C.uint64_t(uintptr(unsafe.Pointer(p)))
}

Fazit++

Wenn etwas abgedrehtes mal nicht zu gehen scheint, dann gebe ich doch nicht auf, sondern ich mache es einfach noch abgedrehter. Impossible is nothing.

Wenn Du auch lernen willst, wie man unmögliches möglich macht, komm auf unsere Seite der Macht.

Alexander Klimov
Alexander Klimov
Developer

Alexander hat 2017 seine Ausbildung zum Developer bei NETWAYS erfolgreich abgeschlossen. Als leidenschaftlicher Programmierer und begeisterter Anhänger der Idee freier Software, hat er sich dabei innerhalb kürzester Zeit in die Herzen seiner Kollegen im Development geschlichen. Wäre nicht ausgerechnet Gandhi sein Vorbild, würde er von dort aus daran arbeiten, seinen geheimen Plan, erst die Abteilung und dann die Weltherrschaft an sich zu reißen, zu realisieren - tut er aber nicht. Stattdessen beschreitet er mit der...