0%

Beim Versuch den IAM-Server per IAM-Client zu kontaktieren kommt folgende Fehlermeldung nach dem Aufruf von

1
const iamClient = await IAMClient.createInstance(clientOptions);

Fehler:

1
2
3
4
5
6
7
{ AggregateError:
RequestError: unable to get local issuer certificate
at ClientRequest.req.once.err (/home/notes/Documents/node-api/DLRWebsucheRestAPI/node_modules/openid-client/node_modules/got/index.js:182:22)
RequestError: unable to get local issuer certificate
at ClientRequest.req.once.err (/home/notes/Documents/node-api/DLRWebsucheRestAPI/node_modules/openid-client/node_modules/got/index.js:182:22)
at rejected (/home/notes/Documents/node-api/DLRWebsucheRestAPI/node_modules/p-some/index.js:49:11)
at process._tickCallback (internal/process/next_tick.js:68:7) name: 'AggregateError' }

Da ich in meiner Anwendung mehrere Schnittstellen zu verschiedenen Services verwende und damit auch mehrere Zertifikate, hat es etwas gedauert, bis die Ursache gefunden war. Da wäre dann eine etwas aussagekräftigere Fehlermeldung (welches cert-file?) hilfreich.

Ursache:

Das verwendete Zertifikat des IAM-Service (in der config.js: iam_server_ca_cert ) enthält keine certificate chain sondern nur das Zertifikat.

Lösung:

Die in der config.js unter iam_server_ca_cert angegebene Datei durch ein File ersetzen, das neben dem Zertifikat auch die certificate chain enthält.

Wenn auf der Domino Konsole Fehler diesert Art

1
2
3
4
[011269:000013-00007F06S339F700] 14.07.2020 16:26:06,53 nti_CipherSpecStringToMask> Ignoring invalid SSLCipherSpec value C014
[011269:000013-00007F06S339F700] 14.07.2020 16:26:06,53 nti_CipherSpecStringToMask> Ignoring invalid SSLCipherSpec value 39
[011269:000013-00007F06S339F700] 14.07.2020 16:26:06,53 nti_CipherSpecStringToMask> Ignoring invalid SSLCipherSpec value C013
[011269:000013-00007F06S339F700] 14.07.2020 16:26:06,53 nti_CipherSpecStringToMask> Ignoring invalid SSLCipherSpec value 35

erscheinen, dann hilft es, wie auf hcltech.com geschrieben, die Cipher-Konfiguration im Serverdokument anzupassen. Das funktioniert wunderbar, wenn die Konfiguration im Serverdokument erfolgt.

Heute hatte ich den Fall, dass der Server Internet-Site Dokumente für die Konfiguration nutzte:

ServerDoc

Also passt ich in allen Internet-Site Dokumenten die Ciphers entsprechend der Anleitung von hcltech an und startet den http task neu in der Erwartung, dass die Meldungen ‘Ignoring invalid SSLCipher..’ nicht mehr angezeigt würden. Dies war jedoch nicht der Fall, die Meldungen erschienen weiterhin.

Lösung:
Wenn im Serverdokument zuvor irgendwann einmal SSL-Ciphers konfiguriert waren, dann stehen diese Werte trotz verwendung von Internet-Site Dokumenten noch immer im Dokument. In meinem Fall habe ich die Werte dann einfach mit dem SIT Dexplorer gelöscht und das Dokument neu gespeichert. Nach einem Neustart des http-task wurden die Meldungen nicht mehr angezeigt.

ServerDoc

Wenn man Nginx als Reverse-Proxy einsetzt, schadet es nicht hin und wieder die Konfiguration zu prüfen und ggfs. zu aktualisieren. Gerade der Reverse-Proxy ist ein Kandidat, der eher gerne übersehen wird, weil er i.d.R. unauffällig und transparent seinen Dienst verrichtet. Während Updates aus dem Softwarerepository regelmäßig installiert werden, muss man die Konfiguration selbst prüfen und ggfs. aktualisieren.
Bei einem von mir geprüften Test-Server, der eher selten genutzt wurde, habe ich die folgenden Punkte in der Konfiguration ergänzt. Die Auflistung erhebt natürlich keinen Anspruch auf Vollständigkeit - wie lange ist es her, dass Sie Ihren Reverse-Proxy überprüft haben?

  1. Prüfen, ob http 2 verwendet wird und wenn nicht http2 ergänzen:

    1
    listen 443 ssl http2;

    Über die Entwicklertools im Browser kann geprüft werden, ob http2 tatsächlich verwendet wird:

    DevTools

  2. Hinsichtlich der Sicherheit empfiehlt es sich, seine Website regelmäßig z.B. mit dem SSL Server Test der SSL-Labs zu prüfen. Nach Eingabe der zu prüfenden Domain erhält man hier nach einer kurzen Wartezeit ein Rating inkl. möglicher Optimierungspunkte.

    Wer anstatt eines Ratings einen Fehler erhält bzgl. eines nicht gültigen max-age Wertes, der hat vermutlich HSTS nicht konfiguriert. Eine einfache HSTS Konfiguration kann z.B. so aussehen:

    1
    2
    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

    Weitere Informationen zur HSTS finden sich u.a. in Mozillas MDN web docs

  3. Als Wert für ssl_protocols verwende ich TLSv1.2 mit den im Folgenden aufgeführten Ciphers.
    Vor Poodle und Heartbleed ist man damit sicher, ebenfalls wird weitestgehend auf schwache Ciphers verzichtet. SSL-Labs vergibt hierfür ein A-Rating:

    1
    2
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384';
  4. Um die TLS-Performance von Nginx zu verbessern, kann ein SSL-Session-Cache verwendet werden. Diesen aktiviert man in der Nginx-konfiguration wie folgt:

    1
    2
    3
    #SSL-Sessions Cache verwenden
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1h; # Sessions können 1 Stunde lang wiederverwendet werden
  5. Zur Reduktion der TTFB (Time to first byte) kann die Buffergröße reduziert werden. Der default beträgt 16k.

    1
    ssl_buffer_size 4k;

Beim Einrichten der IAMAccessor-ID für das Identity and Access Management (IAM) von HCL Domino 11 bin ich in der Anleitung beim Punkt ‘Create functional ID for IAM’ auf folgendes Problem gestoßen: Beim Anlegen der ID via ‘Register’ im Domino Administrator erschien die Meldung:

ID upload of the Notes ID Vault failed

Folgende Lösung hat mir in diesem Fall weitergeholfen:

  1. In der Vault-DB (zu finden unter IBM_ID_VAULT<vault-name>.nsf den User IAMAccessor löschen.
  2. Auf der Konsole im Domino Administrator folgenden Befehl absetzen:

    load updall -R IBM_ID_VAULT<vault-name>.nsf

Anschließend konnte ich den User IAMAccessor wie in der Anleitung beschrieben anlegen.

Manche kennen vielleicht noch das Human Genome Project aus den 90ern, das sich zum Ziel gesetzt hatte, mittels Distributed Computing das menschliche Genom zu entschlüsseln (das Ziel wurde 2003 erreicht). Im Jahr 2004 startete das Projekt SETI@Home, das Astrophysikern mittels Analyse der gigantischen Datenmengen, welche Teleskope weltweit liefern, bei der Suche nach außerirdischem Leben helfen soll. In etwa zeitgleich wurde damals von der Berkeley University das Projekt BOINC (Berkeley Open Infrastructure for Network Computing) ins Leben gerufen, das es für normale Anwender möglich machte, mittels Distributed Computing Projekte aus unterschiedlichen Wissenschaftsbereichen durch Spenden von Rechenleistung zu unterstützen.

Wer möchte, kann nun im Kampf gegen Sars-CoV-2 (das Coronavirus, das die Lungenkrankheit Covid-19 auslöst) helfen, indem er sich von folding@home den Client herunterlädt und installiert. Anschließend muss man nur noch unter ‘I support research fighting’ den Eintrag ‘any disease’ auswählen. Der Client kann so konfiguriert werden, dass er nur dann, wenn der PC nicht verwendet wird, Berechnungen durchführt, so dass man als Benutzer nicht beeinträchtigt wird. Folding@home ist ein Projekt der Stanford University und des Stanford University Medical Center.

foldingAtHome Screenshot

Endlich habe ich Zeit gefunden meinen Blog zu modernisieren. Viel Spaß!

Solr config

Um Duplikate aus den Suchergebnissen zu entfernen, bietet Solr eine De-Duplication Funktion. De-Duplication in Solr basiert auf der Idee pro Eintrag im Index einen Hash-Wert basierend auf definierten Feldwerten zu erzeugen und diesen dann als Indikator für Duplikate zu verwenden. Das kann sehr nützlich sein, wenn Inhalte über unterschiedliche Urls erreichbar sind - aus Solr-Perspektive sind dies dann zunächst unterschiedliche Inhalte.
Die Einrichtung von De-Duplicate ist einfach: Zunächst muss in der schema.xml des verwendeten Solr-Cores ein neues Feld definiert werden, welches später den Hash enthalten soll:

1
<field name="signatureField" type="string" stored="true" indexed="true" multiValued="false" />

In der solrconfig.xml des Solr-Cores definieren wir als nächstes eine updateRequestProcessorChain, der wir als signatureClass Lookup3Signatur übergeben (das ist ein Hash-verfahren, das performanter als MD5 ist und somit die Crawl-Vorgänge aufgrund der kürzeren Berechnungszeit beschleunigt). Die Felder, über die der Hash gebildet werden soll, definieren wir unter fields und der Hash selbst soll dann in das Feld signatureField geschrieben werden:

1
2
3
4
5
6
7
8
9
10
11
<updateRequestProcessorChain name="dedupe"> 
<processor class="solr.processor.SignatureUpdateProcessorFactory">
<bool name="enabled">true</bool>
<str name="signatureField">signatureField</str>
<bool name="overwriteDupes">false</bool>
<str name="fields">content</str>
<str name="signatureClass">solr.processor.Lookup3Signature</str>
</processor>
<processor class="solr.LogUpdateProcessorFactory" />
<processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain>

Die neue updateRequestProcessorChain wird anschließend dem update-requestHandler bekanntgemacht:

1
2
3
4
5
<requestHandler name="/update" class="solr.UpdateRequestHandler" >
<lst name="defaults">
<str name="update.chain">dedupe</str>
</lst>
</requestHandler>

Wenn das nächste mal der Index neu geschrieben oder aktualisiert wird, dann hat jeder neue Eintrag im Index ein neues Feld, welches den Hash beinhaltet, der auf dem Inhalt ‘content’ basiert.
Damit doppelte Einträge aus der Suche verschwinden, muss bei Suchanfragen Gebrauch von der Filterfunktion gemacht werden. Das geht ganz einfach indem an die Url folgender Parameter angefügt wird:

fq={!collapse field=signatureField}

Weitere Informationen finden sich in der Apache Solr Dokumentation.

Wer mit Visual Studio Code arbeitet und etwas aufgepimptes Markdown haben möchte, der sollte sich die Erweiterung ‘Markdown Preview Enhanced with litvis’ ansehen. Neben Charts kann man damit auch Ablaufdiagramme erzeugen und diese mit Hilfe von Themes unterschiedlich darstellen lassen:

markdown beautified

Jenkins bietet seine Dienste neben der Weboberfläche ebenfalls via Rest-Api an. Wer bei Verwendung dieser die Sicherheit erhöhen möchte, kann in den Einstellungen von Jenkins u.a. die CSRF Protection aktivieren um, Cross Site Request Forgery Angriffe zu verhindern. Jenkins akzeptiert nach Aktivierung des Features Rest-Service Aufrufe dann nur, wenn dem Request ein Token im Header beigefügt wird (in der Jenkins Dokumentation wird dies ‘crumb’ genannt). Als quick & dirty Lösung kann man mittels wget, wie in der Dokumentation beschrieben, ein Token generieren. Dieses kann man dann z.B. über Properties in die Anwendung einfließen lassen. Das ist eine schnelle Lösung für erste Tests, hat aber neben sicherheitstechnischen Bedenken den Nachteil, dass das Token an die Session gebunden ist: wird die Anwendung, die die Rest-Aufrufe durchführt in der Testumgebung beispielsweise auf verschiedene Applicationservern parallel getestet und dabei das gleiche Token verwendet, so erhält der zweite Aufrufer einen Status 403 zurück: invalid crumb.

Viel besser ist es vor jedem Rest-Aufruf ein gültiges Token von Jenkins anzufordern - Jenkins bietet hierfür einen eigenen Rest-Service, der wahlweise im json oder xml Format ein Token zurückliefert. Es bietet sich an hierfür eine eigene Funktion zu schreiben, die bei Bedarf den Tokennamen sowie das Token selbst zurückliefert:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Before a jenkins rest service can be called, we need to get a crumb from jenkins. This crumb needs to be added to the http-header when calling a rest service.
*
* @return m - a Map containg the name of the crumb and the value of the crumb as Strings.
* @throws Exception
*/
private Map<String, String> getCrumbFromJenkins() throws Exception
{
HashMap<String, String> m = new HashMap<String, String>();

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + getAuthenticationCode());

HttpEntity<String> request = new HttpEntity<String>(headers);
RestTemplate restTemplate = new RestTemplate();

ResponseEntity<Object> response = restTemplate.exchange("https://dev.meine-domain.de:8090/crumbIssuer/api/json", HttpMethod.GET, request, Object.class);
CrumbJson crumbJson = new Gson().fromJson(response.getBody().toString(), CrumbJson.class);

m.put("name", crumbJson.crumbRequestField);
m.put("value", crumbJson.crumb);

return m;
}

public class CrumbJson {
public String crumb;
public String crumbRequestField;
}

Zur Zeit arbeite ich an einem spannenden Projekt: Ziel ist die Erstellung einer Java basierten Webanwendung, die verschiedene Workflows inkl. LDAP-basiertem Rechtemanagement für Softwarereleases abbildet. Im Kontext dieser Workflows müssen Daten aus unterschiedlichen Quellen (z.B. SVN, Git, Jenkins) abgerufen, aufbereitet, paketiert und in definierten Formaten an bestimmte Ziele (FTP-Server, Network-shares etc.) geschickt werden. U.a. ist als Format hier das ZIP-Format (mit Verschlüsselung) vorgesehen.

Das klingt zunächst sehr simpel, jedoch unterstützt Java in der Version 8 (Projektvorgabe) zwar das Erstellen von zip Files (package: java.util.zip), jedoch keine Verschlüsselung. Ein Blick auf mvnrepository.com liefert hierzu einige Treffer.

Mit am populärsten dürfte sicherlich ‘Apache Commons Compress’ sein, jedoch zeigt ein Blick in die Dokumentation, dass die Verschlüsselung von Zip-files nicht unterstützt wird (wohl aber von anderen Formaten wie 7z, was mir jedoch leider nicht weiter hilft). Gleiches gilt für ‘Snappy Java’, hier lässt sich bereits in der Liste der Features kein Wort zum Thema Verschlüsselung finden. Nach einem kurzen Vergleich der unterschiedlichen Bibliotheken habe ich mich schließlich für Zip4J von Lingala entschieden. Die Verwendung ist simpel, jedoch enthält der Konstruktor der Klasse ZipFile, der einen String als Argument entgegen nimmt, meiner Ansicht nach einen Fehler:

1
2
ZipFile zip = new ZipFile("C:\\test\\test.zip");
zip.addFile(srcFile, params);
1
2
Exception:
net.lingala.zip4j.exception.ZipException: Probably not a zip file or a corrupted zip file

Wie ich im Dateisystem sehen kann, wird zwar eine Datei test.zip erzeugt, diese ist jedoch leer und besitzt eine Größe von 0kb.
Nach einigen Tests habe ich festgestellt, dass der andere Konstruktor, der ein File-Objekt als Argument erwartet, besser funktioniert. Zunächst liefert

1
2
File ziel = new File("C:\\test\\", "test.zip");
ZipFile zip = new ZipFile(ziel.getAbsoluteFile());

jedoch wieder die gleiche Exception. Mangelnde Rechte im Dateisystem seitens der Anwendung habe ich durch einen kurzen Test ausgeschlossen, ein

1
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("C:\\test\\test.zip"))

legt problemlos die gewünschte Zip-Datei (allerdings unverschlüsselt) an und auch ein anschließendes Befüllen des Zips ist möglich. Nach einigen Tests hat sich herausgestellt, dass das Problem mit dem Pfad zu tun hat, denn die Verwendung des temp-Dirs funktioniert hier einwandfrei:

1
2
3
File ziel = new File(System.getProperty("java.io.tmpdir"), "test.zip");
ZipFile zip = new ZipFile(ziel.getAbsoluteFile());
zip.addFile(srcFile, params);

Wie erwähnt, ermöglicht es Zip4J verschlüsselte Zip-Archive zu erstellen. Hierzu muss lediglich ein ZipParameters-Objekt erzeugt und als zweites Argument z.B. an die addFile()-Funktion übergeben werden:

1
2
3
4
5
6
7
ZipParameters params = new ZipParameters();
params.setCompressionLevel(Zip4jConstants.COMP_DEFLATE);
params.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_MAXIMUM);
params.setEncryptFiles(true);
params.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
params.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
params.setPassword("Passw0rt");

Die maximal unterstützte Schlüssellänge im Fall von AES beträgt 256 bit. In Bezug auf das verschlüsselte zip-File ist zu beachten, dass nur die einzelnen im Archiv enthaltenen Dateien verschlüsselt werden, nicht jedoch der Index, d.h. eine Auflistung der Dateinamen ist auch ohne Passworteingabe möglich.

Sofern jemand noch eine bessere Lösung für das Generieren von verschlüsselten Zip-Archiven kennt, würde ich mich über einen entsprechenden Hinweisen in den Kommentaren freuen!