Lokale Entwicklung

Immer mehr Anwendungen setzten SSL voraus, um richtig zu funktionieren. Die Möglichkeit selbstsignierte Zertifikate zu nutzen kann man in Betracht ziehen, ist aber der Erfahrung nach auch nicht die Lösung, das es viele zusätzliche Probleme bereitet.

Also wäre es gut auch auf einem Entwicklungsrechner mit TLS/SSL zu arbeiten.

NginX Reverse Proxy

Um die lokalen Server mit SSL abzusichern benötigt man nicht viel. Im Grunde braucht man zunächst ein Wildcard Zertifikat. Wenn dieses vorliegt, dann ist die größte Hürde bereits genommen.

Was ihr noch braucht ist Docker, aber das sollte mittlerweile auch auf jedem Rechner vorhanden sein.

Konfiguration

Erstellt ein neues Verzeichnis und kopiert den privaten Key und das fullchain.cer z.B. von LetEncrypt mit in das Verzeichnis.

Erstellt eine docker-compose.yaml mit folgendem Inhalt:

version: "3.5"
services:
  reverseproxy:
    image: nginx
    ports:
      - "443:443"
    restart: always
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./fullchain.cer:/etc/nginx/fullchain.cer
      - ./private.key:/etc/nginx/private.key

Was nun noch fehlt ist die NginX Konfiguration. Dieses ist auch recht einfach gehalten. Speichert die nginx.conf so ab:

user nginx;
worker_processes 1;

pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

error_log /dev/stdout debug;

http {
    # https redirect
    server {
        listen 80;
        server_name "~^k8s-.+\.domain\.de$";
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name "~^k8s-.+\.domain\.de$";

        ssl on;
        ssl_certificate     /etc/nginx/fullchain.cer; # fullchain
        ssl_certificate_key /etc/nginx/private.key;   # private key

        #
        # Serve Kubernetes cluster. Forward to ingress controller
        #
        location /
        {
            proxy_set_header        Host $host;
            proxy_set_header        X-Real-IP $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        X-Forwarded-Proto $scheme;

            # static IP address of K3D cluster
            proxy_pass              http://192.168.2.100:4000/; # ACHTUNG: Nicht localhost!!! Das wäre der Docker Container.

            proxy_read_timeout      600s;
            proxy_send_timeout      600s;
        }
    }
}

Hier muss die Host Addresse (also die IP des Entwicklungsrechner) und der Port angegeben werden. In dem Beispiel ist es ein lokaler Kubernetes Cluster. Über die RegEx nimmt NginX im Server Block alle Host der Doamin entgegen.

DNS

Zu guter letzt müsst ihr für DNS Einträge sorgen. Am einfachsten über die /etc/hosts Datei. Dann sollte ein gesicherter Aufruf mit HTTPS möglich sein.

Skopeo

Das CLI Tool Skopeo ist ein nützlicher Helfer, wenn es um Arbeiten mit Images insbesondere das Kopieren geht.

Installation

Die Installation unter Manjaro erfolgt mit yay oder Pacman.

  yay -S skopeo

bzw. mit pacman

  pacman -S skopeo

Beispiele

Die Anwendungsmöglichkeiten sind vielfältig. Ich werde diesen Artikel später erweitern.

Kopieren eines Containerimage

Als Beispiel verwende ich hier eine sehr einfache lokale Registry, um Kopien von Images zu hosten. In diese Registry sollen zur Anschauung ein Image kopiert werden.

Setup lokale Registry

Zunächst benötigen wir für das Beispiel eine lokale Registry. Diese ist mit Docker schnell aufgesetzt. Die lokale Registry lauscht auf dem Port 5000.

docker container run -d --name registry.localhost -v local_registry:/var/lib/registry --restart always -p 5000:5000 registry:2

Nun kann man unter localhost:5000 die Registry verwenden. Um in die Logs zu schauen verwende ich lazydocker. Dieses ermöglich mir einen einfachen Einblick in den Container und die Logausgaben. Weitere coole Features kann man unter lazydocker auf GitHub nachlesen.

Image mit Skopeo kopieren

Mit dem copy Parameter können Images bequem von einer Registry zu einer anderen kopiert werden. In dem Beispiel wollen wir das offizielle Nginx Image in die lokale (insecure registry) kopieren. Da es eine unsichere Registry ist und nur HTTP spricht, muss der Parameter –dest-tls-verify=false gesetzt sein, da sonst keine Verbindung mit dem HTTP Protokoll zu Satnde kommt.

skopeo copy --dest-tls-verify=false docker://nginx:latest docker://registry.localhost:5000/nginx:latest

Kopie von Skopeo überprüfen

Das neu kopierte Image kann schnell mit curl überprüft werden.

curl -s localhost:5000/v2/_catalog | jq
{
  "repositories": [
    "nginx"
  ]
}

Trivy

Trivy ist ein kleines in GO geschriebenes Programm. Die Installation unter Manjaro oder Arch erfolgt mir yay und ist schnell erledigt.

yay -S --noconfirm trivy

Was macht Trivy?

Trivy ist ein Securityscanner und untersucht Docker Images auf mögliche CVEs und gibt das Ergebnis in tabellarischer Form auf der Konsole aus.

Der Erste Check

Der Erster Check eines Images dauer in der Regel etwas länger, da die Datenbank zunächst aktualisiert werden muss.

2020-09-03T08:16:52.070+0200    WARN    You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2020-09-03T08:16:52.088+0200    INFO    Need to update DB
2020-09-03T08:16:52.088+0200    INFO    Downloading DB...
18.25 MiB / 18.25 MiB [-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 2.38 MiB p/s 8s

Auch ein folgender Scan sollte schneller ablaufen, da Trivy die Scanergebnisse einzelner Layer in dem Docker Image im Cache zwischenspeichert.

Lazydocker

Lazydocker macht das Handling mit Docker auf der Konsole einfach. Es bietet wie andere Tools K9S eine TUI.

Die Installation von Lazydocker

Unter Manjaro ist die Installation mit yay recht einfach:

yay -S --noconfirm lazydocker

Funkionsumfang

Lazydocker enthält die wichtigsten Funktionen, um schnell Container, Images und Volumes im Docker zu verwalten und von laufenden Container den Status abzufragen.

Dazu gehören die CPU und das Memory der Container, das Log, die Konfiguration und die TOP Prozesse.

Bedienung von Lazydocker

Der Bildschirm ist zweispaltig aufgeteilt. In der linken Spalte befinden sich die Frames für das Projekt, Containers, Images und Volumes.

Mit den Tasten [ und ] lassen sich in den Frames zwischen den Tabs springen. Es kann aber auch die Maus genutzt werden.

Mit der Taste x lässt sich das Kontext-Menü öffnen. Hierüber lassen sich bequem die Funktionen erkunden.

Bulk-Operations

Ein interresantes Feature sind die Bulk-Operations. Diese bieten in den unterschiedlichen Frames verschiedene Massenoperationen an. Z.B werden im Images Frames das Löschen den ungenutzten Images angeboten.

Eine Bulk Operation wird mit b gestartet. Ein Popupmenü gibt dann weitere Anweisungen.

Lokales Docker Repository verwenden

Möchte man Docker Images zum Bauen einer Anwendung in Drone verwenden, dann muss i.d.R. Credentials für Basic Auth am Nexus Docker Repository übergeben werden. Dazu muss den Inhalt der config.json in ein neues Secret in Drone schreiben.

Dazu muss man sich an der Konsole einmal an dem Repository docker login anmelden.

docker login docker.XXXX.org

Das JSON der config Datei sieht in etwa dann so aus:

        "auths": {
                "docker.XXXX.org": {
                        "auth": "VXXXXXXX="
                }
        },

Den Inhalt kopieren sie bitte in ein neues Secret mit dem Key dockerconfigjson. Aus diesem wird Drone die Informationen für die Basic Authentication an dem Docker Repository extrahieren. In der .drone.yml muss nur noch image_pull_secrets eingefügt werden.

kind: pipeline
name: default

steps:
- name: setup
  image: docker.XXXX.org/edvpfau/archlinux-base-image:latest
  environment:
    NEXUS_URL:
      from_secret: NEXUS_URL
    NEXUS_USERNAME:
      from_secret: NEXUS_USERNAME
    NEXUS_PASSWORD:
      from_secret: NEXUS_PASSWORD
    NEXUS_REPOSITORY:
      from_secret: NEXUS_REPOSITORY
    SONAR_HOST:
      from_secret: SONAR_HOST
    SONAR_TOKEN:
      from_secret: SONAR_TOKEN
  commands:
    - JAVA_HOME=/usr/lib/jvm/default ./gradlew -Dsonar.host.url=$SONAR_HOST -Dsonar.login=$SONAR_TOKEN clean build test sonarqube

image_pull_secrets:
- dockerconfigjson

Nun steht der Nutzung eigener privater Images zum Bauen der Software nichts mehr im Wege. Das Image kann dann speziell an die Verwendete Software angepasst werden. Hier in dem Beispiel verwende ich ein Image, welches den Gradle Wrapper vorinstalliert hat und somit in jedem Durchgang nicht nur Zeit sondern auch Netzwerktraffic eingesparrt werden.

Sonarqube im Docker Container

Da es nicht ganz so einfach, wie erhofft war, den Docker Container mit einem Docker Compose Skript zu starten, habe ich mir überlegt das Prozedere hier kurz zu beschreiben. Ich möchte die aktuelle Comunity-Beta Version von Sonar einsetzen.

First Start – Sonarqube vorbereiten

Leider funktioniert das Docker Image für die Initialisierung nicht, da es Probleme mit den Berechtigungen gibt. Daher habe ich das folgende Bash Skript geschrieben. So sollte sich Sonar korrekt initialisieren lassen. Es wird die Konfiguration in das Conf Verzeichnis kopiert.

Erstellen sie einen Ordner in ~/docker/sonar und erzeugen sie die Datei init.sh mit folgenden Inhalt.

#!/bin/bash

# prepare directory structure
mkdir -p volumes/conf
mkdir -p volumes/extensions
mkdir -p volumes/data
chmod -R 777 ./volumes 

# run sonar init
docker run --rm \
-v $PWD/volumes/conf:/opt/sonarqube/conf \
-v $PWD/volumes/extensions:/opt/sonarqube/extensions \
-v $PWD/volumes/data:/opt/sonarqube/data \
sonarqube:community-beta --init

# fix permissions again
find ./volumes -type d -exec sudo chmod 777 {} + 

# copy sonar properties into conf dir
sudo cp sonar.properties ./volumes/conf/

Docker-Compose Skript

Sonar wird auf dem Port 9000 gestartet. Es werden unterhalb des Ordeners ./volumes drei Verzeichnisse gemounted. In dem Verzeichnis conf liegt die Konfigurationsdatei sonar.properties. Hier wird Sonarqube konfiguriert. Da nach dem Initialisieren der normale Nutzer keine Berechtigung hat, liegt das Original im Verzeichnis ~/docker/sonar. Diese wird durch das Init-Skript kopiert.

version: "2"

services:
  sonarqube:
    image: sonarqube:community-beta
    ports:
      - "9000:9000"
    networks:
      - sonarnet
    environment:
      - sonar.jdbc.username=sonar
      - sonar.jdbc.password=sonar
      - sonar.jdbc.url=jdbc:postgresql://db:5432/sonar
    volumes:
      - ./volumes/conf:/opt/sonarqube/conf
      - ./volumes/data:/opt/sonarqube/data
      - ./volumes/extensions:/opt/sonarqube/extensions
    restart: always
    depends_on:
      - db

  db:
    image: postgres
    networks:
      - sonarnet
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
    volumes:
      - ./volumes/postgresql:/var/lib/postgresql
      # This needs explicit mapping due to https://github.com/docker-library/postgres/blob/4e48e3228a30763913ece952c611e5e9b95c8759/Dockerfile.template#L52
      - ./volumes/postgresql_data:/var/lib/postgresql/data
    restart: always

networks:
  sonarnet:
    driver: bridge

Die Konfiguration

Erstellen sie die Konfigurationsdatei sonar.properties mit folgenden Inhalt und passen sie sie ggf. noch an. Die Grundeinstellungen für die Datenbankverbindung sind bereits in den Environment Variablen von Docker gesetzt, sodass sie nun Sonarqube mit ./init.sh && docker-compose up -d starten können.

# Property values can:
# - reference an environment variable, for example sonar.jdbc.url= ${env:SONAR_JDBC_URL}
# - be encrypted. See https://redirect.sonarsource.com/doc/settings-encryption.html

#--------------------------------------------------------------------------------------------------
# DATABASE
#
# IMPORTANT:
# - The embedded H2 database is used by default. It is recommended for tests but not for
#   production use. Supported databases are Oracle, PostgreSQL and Microsoft SQLServer.
# - Changes to database connection URL (sonar.jdbc.url) can affect SonarSource licensed products.

# User credentials.
# Permissions to create tables, indices and triggers must be granted to JDBC user.
# The schema must be created first.
#sonar.jdbc.username=sonar
#sonar.jdbc.password=sonar

#----- Embedded Database (default)
# H2 embedded database server listening port, defaults to 9092
#sonar.embeddedDatabase.port=9092


#----- Oracle 11g/12c/18c/19c
# The Oracle JDBC driver must be copied into the directory extensions/jdbc-driver/oracle/.
# Only the thin client is supported, and we recommend using the latest Oracle JDBC driver. See
# https://jira.sonarsource.com/browse/SONAR-9758 for more details.
# If you need to set the schema, please refer to http://jira.sonarsource.com/browse/SONAR-5000
#sonar.jdbc.url=jdbc:oracle:thin:@localhost:1521/XE


#----- PostgreSQL 9.3 or greater
# By default the schema named "public" is used. It can be overridden with the parameter "currentSchema".
#sonar.jdbc.url=jdbc:postgresql://db/sonarqube?currentSchema=my_schema


#----- Microsoft SQLServer 2014/2016/2017 and SQL Azure
# A database named sonar must exist and its collation must be case-sensitive (CS) and accent-sensitive (AS)
# Use the following connection string if you want to use integrated security with Microsoft Sql Server
# Do not set sonar.jdbc.username or sonar.jdbc.password property if you are using Integrated Security
# For Integrated Security to work, you have to download the Microsoft SQL JDBC driver package from
# https://www.microsoft.com/en-us/download/details.aspx?id=55539
# and copy sqljdbc_auth.dll to your path. You have to copy the 32 bit or 64 bit version of the dll
# depending upon the architecture of your server machine.
#sonar.jdbc.url=jdbc:sqlserver://localhost;databaseName=sonar;integratedSecurity=true

# Use the following connection string if you want to use SQL Auth while connecting to MS Sql Server.
# Set the sonar.jdbc.username and sonar.jdbc.password appropriately.
#sonar.jdbc.url=jdbc:sqlserver://localhost;databaseName=sonar


#----- Connection pool settings
# The maximum number of active connections that can be allocated
# at the same time, or negative for no limit.
# The recommended value is 1.2 * max sizes of HTTP pools. For example if HTTP ports are
# enabled with default sizes (50, see property sonar.web.http.maxThreads)
# then sonar.jdbc.maxActive should be 1.2 * 50 = 60.
#sonar.jdbc.maxActive=60

# The maximum number of connections that can remain idle in the
# pool, without extra ones being released, or negative for no limit.
#sonar.jdbc.maxIdle=5

# The minimum number of connections that can remain idle in the pool,
# without extra ones being created, or zero to create none.
#sonar.jdbc.minIdle=2

# The maximum number of milliseconds that the pool will wait (when there
# are no available connections) for a connection to be returned before
# throwing an exception, or <= 0 to wait indefinitely.
#sonar.jdbc.maxWait=5000

#sonar.jdbc.minEvictableIdleTimeMillis=600000
#sonar.jdbc.timeBetweenEvictionRunsMillis=30000



#--------------------------------------------------------------------------------------------------
# WEB SERVER
# Web server is executed in a dedicated Java process. By default heap size is 512MB.
# Use the following property to customize JVM options.
#    Recommendations:
#
#    The HotSpot Server VM is recommended. The property -server should be added if server mode
#    is not enabled by default on your environment:
#    http://docs.oracle.com/javase/8/docs/technotes/guides/vm/server-class.html
#
#    Startup can be long if entropy source is short of entropy. Adding
#    -Djava.security.egd=file:/dev/./urandom is an option to resolve the problem.
#    See https://wiki.apache.org/tomcat/HowTo/FasterStartUp#Entropy_Source
#
#sonar.web.javaOpts=-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError

# Same as previous property, but allows to not repeat all other settings like -Xmx
#sonar.web.javaAdditionalOpts=

# Binding IP address. For servers with more than one IP address, this property specifies which
# address will be used for listening on the specified ports.
# By default, ports will be used on all IP addresses associated with the server.
#sonar.web.host=0.0.0.0

# Web context. When set, it must start with forward slash (for example /sonarqube).
# The default value is root context (empty value).
#sonar.web.context=
# TCP port for incoming HTTP connections. Default value is 9000.
#sonar.web.port=9000


# The maximum number of connections that the server will accept and process at any given time.
# When this number has been reached, the server will not accept any more connections until
# the number of connections falls below this value. The operating system may still accept connections
# based on the sonar.web.connections.acceptCount property. The default value is 50.
#sonar.web.http.maxThreads=50

# The minimum number of threads always kept running. The default value is 5.
#sonar.web.http.minThreads=5

# The maximum queue length for incoming connection requests when all possible request processing
# threads are in use. Any requests received when the queue is full will be refused.
# The default value is 25.
#sonar.web.http.acceptCount=25

# By default users are logged out and sessions closed when server is restarted.
# If you prefer keeping user sessions open, a secret should be defined. Value is
# HS256 key encoded with base64. It must be unique for each installation of SonarQube.
# Example of command-line:
# echo -n "type_what_you_want" | openssl dgst -sha256 -hmac "key" -binary | base64
#sonar.auth.jwtBase64Hs256Secret=

# The inactivity timeout duration of user sessions, in minutes. After the configured
# period of time, the user is logged out.
# The default value is set to 3 days (4320 minutes)
# and cannot be greater than 3 months. Value must be strictly positive.
#sonar.web.sessionTimeoutInMinutes=4320

# A passcode can be defined to access some web services from monitoring
# tools without having to use the credentials of a system administrator.
# Check the Web API documentation to know which web services are supporting this authentication mode.
# The passcode should be provided in HTTP requests with the header "X-Sonar-Passcode".
# By default feature is disabled.
#sonar.web.systemPasscode=


#--------------------------------------------------------------------------------------------------
# SSO AUTHENTICATION

# Enable authentication using HTTP headers
#sonar.web.sso.enable=false

# Name of the header to get the user login.
# Only alphanumeric, '.' and '@' characters are allowed
#sonar.web.sso.loginHeader=X-Forwarded-Login

# Name of the header to get the user name
#sonar.web.sso.nameHeader=X-Forwarded-Name

# Name of the header to get the user email (optional)
#sonar.web.sso.emailHeader=X-Forwarded-Email

# Name of the header to get the list of user groups, separated by comma (optional).
# If the sonar.sso.groupsHeader is set, the user will belong to those groups if groups exist in SonarQube.
# If none of the provided groups exists in SonarQube, the user will only belong to the default group.
# Note that the default group will always be set.
#sonar.web.sso.groupsHeader=X-Forwarded-Groups

# Interval used to know when to refresh name, email and groups.
# During this interval, if for instance the name of the user is changed in the header, it will only be updated after X minutes.
#sonar.web.sso.refreshIntervalInMinutes=5

#--------------------------------------------------------------------------------------------------
# LDAP CONFIGURATION

# Enable the LDAP feature
# sonar.security.realm=LDAP

# Set to true when connecting to a LDAP server using a case-insensitive setup.
# sonar.authenticator.downcase=true

# URL of the LDAP server. Note that if you are using ldaps, then you should install the server certificate into the Java truststore.
# ldap.url=ldap://localhost:10389

# Bind DN is the username of an LDAP user to connect (or bind) with. Leave this blank for anonymous access to the LDAP directory (optional)
# ldap.bindDn=cn=sonar,ou=users,o=mycompany

# Bind Password is the password of the user to connect with. Leave this blank for anonymous access to the LDAP directory (optional)
# ldap.bindPassword=secret

# Possible values: simple | CRAM-MD5 | DIGEST-MD5 | GSSAPI See http://java.sun.com/products/jndi/tutorial/ldap/security/auth.html (default: simple)
# ldap.authentication=simple

# See :
#   * http://java.sun.com/products/jndi/tutorial/ldap/security/digest.html
#   * http://java.sun.com/products/jndi/tutorial/ldap/security/crammd5.html
# (optional)
# ldap.realm=example.org

# Context factory class (optional)
# ldap.contextFactoryClass=com.sun.jndi.ldap.LdapCtxFactory

# Enable usage of StartTLS (default : false)
# ldap.StartTLS=true

# Follow or not referrals. See http://docs.oracle.com/javase/jndi/tutorial/ldap/referral/jndi.html (default: true)
# ldap.followReferrals=false

# USER MAPPING

# Distinguished Name (DN) of the root node in LDAP from which to search for users (mandatory)
# ldap.user.baseDn=cn=users,dc=example,dc=org

# LDAP user request. (default: (&(objectClass=inetOrgPerson)(uid={login})) )
# ldap.user.request=(&(objectClass=user)(sAMAccountName={login}))

# Attribute in LDAP defining the user’s real name. (default: cn)
# ldap.user.realNameAttribute=name

# Attribute in LDAP defining the user’s email. (default: mail)
# ldap.user.emailAttribute=email

# GROUP MAPPING

# Distinguished Name (DN) of the root node in LDAP from which to search for groups. (optional, default: empty)
# ldap.group.baseDn=cn=groups,dc=example,dc=org

# LDAP group request (default: (&(objectClass=groupOfUniqueNames)(uniqueMember={dn})) )
# ldap.group.request=(&(objectClass=group)(member={dn}))

# Property used to specifiy the attribute to be used for returning the list of user groups in the compatibility mode. (default: cn)
# ldap.group.idAttribute=sAMAccountName

#--------------------------------------------------------------------------------------------------
# COMPUTE ENGINE
# The Compute Engine is responsible for processing background tasks.
# Compute Engine is executed in a dedicated Java process. Default heap size is 512MB.
# Use the following property to customize JVM options.
#    Recommendations:
#
#    The HotSpot Server VM is recommended. The property -server should be added if server mode
#    is not enabled by default on your environment:
#    http://docs.oracle.com/javase/8/docs/technotes/guides/vm/server-class.html
#
#sonar.ce.javaOpts=-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError

# Same as previous property, but allows to not repeat all other settings like -Xmx
#sonar.ce.javaAdditionalOpts=


#--------------------------------------------------------------------------------------------------
# ELASTICSEARCH
# Elasticsearch is used to facilitate fast and accurate information retrieval.
# It is executed in a dedicated Java process. Default heap size is 512MB.
#
# --------------------------------------------------
# Word of caution for Linux users on 64bits systems
# --------------------------------------------------
# Please ensure Virtual Memory on your system is correctly configured for Elasticsearch to run properly
# (see https://www.elastic.co/guide/en/elasticsearch/reference/5.5/vm-max-map-count.html for details).
#
# When SonarQube runs standalone, a warning such as the following may appear in logs/es.log:
#      "max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]"
# When SonarQube runs as a cluster, however, Elasticsearch will refuse to start.
#

# JVM options of Elasticsearch process
#sonar.search.javaOpts=-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError

# Same as previous property, but allows to not repeat all other settings like -Xmx
#sonar.search.javaAdditionalOpts=

# Elasticsearch port. Default is 9001. Use 0 to get a free port.
# As a security precaution, should be blocked by a firewall and not exposed to the Internet.
#sonar.search.port=9001

# Elasticsearch host. The search server will bind this address and the search client will connect to it.
# Default is loopback address.
# As a security precaution, should NOT be set to a publicly available address.
#sonar.search.host=


#--------------------------------------------------------------------------------------------------
# UPDATE CENTER

# Update Center requires an internet connection to request https://update.sonarsource.org
# It is enabled by default.
#sonar.updatecenter.activate=true

# HTTP proxy (default none)
#http.proxyHost=
#http.proxyPort=
# HTTPS proxy (defaults are values of http.proxyHost and http.proxyPort)
#https.proxyHost=
#https.proxyPort=

# NT domain name if NTLM proxy is used
#http.auth.ntlm.domain=

# SOCKS proxy (default none)
#socksProxyHost=
#socksProxyPort=

# Proxy authentication (used for HTTP, HTTPS and SOCKS proxies)
#http.proxyUser=
#http.proxyPassword=

# Proxy exceptions: list of hosts that can be accessed without going through the proxy
#                   separated by the '|' character, wildcard character '*' can be used for pattern matching
#                   used for HTTP and HTTPS (default none)
#                   (note: localhost and its literal notations (127.0.0.1, ...) are always excluded)
#http.nonProxyHosts=


#--------------------------------------------------------------------------------------------------
# LOGGING

# SonarQube produces logs in 4 logs files located in the same directory (see property sonar.path.logs below),
# one per process:
#   Main process (aka. App) logs in sonar.log
#   Web Server (aka. Web) logs in web.log
#   Compute Engine (aka. CE) logs in ce.log
#   Elasticsearch (aka. ES) logs in es.log
#
# All 4 files follow the same rolling policy (see sonar.log.rollingPolicy and sonar.log.maxFiles) but it applies
# individually (eg. if sonar.log.maxFiles=4, there can be at most 4 of each files, ie. 16 files in total).
#
# All 4 files have logs in the same format:
#           1           2    3           4                       5                                                   6
# |-----------------| |---| |-|--------------------||------------------------------| |------------------------------------------------------------------------------------------------------------------------------|
# 2016.11.16 16:47:00 INFO  ce[AVht0dNXFcyiYejytc3m][o.s.s.c.t.CeWorkerCallableImpl] Executed task | project=org.sonarqube:example-java-maven | type=REPORT | id=AVht0dNXFcyiYejytc3m | submitter=admin | time=1699ms
#
# 1: timestamp. Format is YYYY.MM.DD HH:MM:SS
#    YYYY: year on 4 digits
#    MM: month on 2 digits
#    DD: day on 2 digits
#    HH: hour of day on 2 digits in 24 hours format
#    MM: minutes on 2 digits
#    SS: seconds on 2 digits
# 2: log level.
#    Possible values (in order of descending criticality): ERROR, WARN, INFO, DEBUG and TRACE
# 3: process identifier. Possible values: app (main), web (Web Server), ce (Compute Engine) and es (Elasticsearch)
# 4: SQ thread identifier. Can be empty.
#    In the Web Server, if present, it will be the HTTP request ID.
#    In the Compute Engine, if present, it will be the task ID.
# 5: logger name. Usually a class canonical name.
#    Package names are truncated to keep the whole field to 20 characters max
# 6: log payload. Content of this field does not follow any specific format, can vary in length and include line returns.
#    Some logs, however, will follow the convention to provide data in payload in the format " | key=value"
#    Especially, log of profiled pieces of code will end with " | time=XXXXms".

# Global level of logs (applies to all 4 processes).
# Supported values are INFO (default), DEBUG and TRACE
#sonar.log.level=INFO

# Level of logs of each process can be controlled individually with their respective properties.
# When specified, they overwrite the level defined at global level.
# Supported values are INFO, DEBUG and TRACE
#sonar.log.level.app=INFO
#sonar.log.level.web=INFO
#sonar.log.level.ce=INFO
#sonar.log.level.es=INFO

# Path to log files. Can be absolute or relative to installation directory.
# Default is <installation home>/logs
#sonar.path.logs=logs

# Rolling policy of log files
#    - based on time if value starts with "time:", for example by day ("time:yyyy-MM-dd")
#      or by month ("time:yyyy-MM")
#    - based on size if value starts with "size:", for example "size:10MB"
#    - disabled if value is "none".  That needs logs to be managed by an external system like logrotate.
#sonar.log.rollingPolicy=time:yyyy-MM-dd

# Maximum number of files to keep if a rolling policy is enabled.
#    - maximum value is 20 on size rolling policy
#    - unlimited on time rolling policy. Set to zero to disable old file purging.
#sonar.log.maxFiles=7

# Access log is the list of all the HTTP requests received by server. If enabled, it is stored
# in the file {sonar.path.logs}/access.log. This file follows the same rolling policy as other log file
# (see sonar.log.rollingPolicy and sonar.log.maxFiles).
#sonar.web.accessLogs.enable=true

# Format of access log. It is ignored if sonar.web.accessLogs.enable=false. Possible values are:
#    - "common" is the Common Log Format, shortcut to: %h %l %u %user %date "%r" %s %b
#    - "combined" is another format widely recognized, shortcut to: %h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"
#    - else a custom pattern. See http://logback.qos.ch/manual/layouts.html#AccessPatternLayout.
# The login of authenticated user is not implemented with "%u" but with "%reqAttribute{LOGIN}" (since version 6.1).
# The value displayed for anonymous users is "-".
# The SonarQube's HTTP request ID can be added to the pattern with "%reqAttribute{ID}" (since version 6.2).
# If SonarQube is behind a reverse proxy, then the following value allows to display the correct remote IP address:
#sonar.web.accessLogs.pattern=%i{X-Forwarded-For} %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}" "%reqAttribute{ID}"
# Default value (which was "combined" before version 6.2) is equivalent to "combined + SQ HTTP request ID":
#sonar.web.accessLogs.pattern=%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}" "%reqAttribute{ID}"


#--------------------------------------------------------------------------------------------------
# OTHERS

# Delay in seconds between processing of notification queue. Default is 60 seconds.
#sonar.notifications.delay=60

# Paths to persistent data files (embedded database and search index) and temporary files.
# Can be absolute or relative to installation directory.
# Defaults are respectively <installation home>/data and <installation home>/temp
#sonar.path.data=data
#sonar.path.temp=temp

# Telemetry - Share anonymous SonarQube statistics
# By sharing anonymous SonarQube statistics, you help us understand how SonarQube is used so we can improve the product to work even better for you.
# We don't collect source code or IP addresses. And we don't share the data with anyone else.
# To see an example of the data shared: login as a global administrator, call the WS api/system/info and check the Statistics field.
#sonar.telemetry.enable=true


#--------------------------------------------------------------------------------------------------
# DEVELOPMENT - only for developers
# The following properties MUST NOT be used in production environments.

# Elasticsearch HTTP connector
#sonar.search.httpPort=-1

Sonarqube in Kubernetes

In dem Artikel Sonarqube in Kubernetes ist eine aktuelle Anleitung für die Installation per Helm Chart.

Portainer

Portainer ist eine WebAnwendung für die Verwaltung von Docker Containern. Portainer ist selbst als Docker Container zu erhalten und somit ist die Installation auch sehr einfach.

Das Remote API aktivieren

Für lokale Docker Installationen ist nichts weiter nötig, da Portainer direkt auf den Socket von dem Docker Daemon zugreift. Möchte man zusätzlich auch entfernte Docker Daemon ansteuern, so muss zunächst

sudo mkdir /etc/systemd/system/docker.service.d
sudo tilde /etc/systemd/system/docker.service.d/startup_options.conf

# /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376

Änderung bekanntmachen

Der Docker Daemon muss nun nur noch neu gestartet werden, dann kann Portainer konfiguriert werden.

sudo systemctl daemon-reload
sudo systemctl restart docker.service

Den Endpoint in Portainer hinzufügen

Nun, da wir den Port 2376 im Netzwerk bereitstellen, kann Portainer auf den Remote Dockerserver zugreifen.

KeyCloak

Der KeyClaok von JBoss ist ein IM (Identity Management) Tool. Mit KeyClaok lassen sich einfach SSO (Single Sign on) Lösungen mit verschiedenen Protokollen umsetzen. Dazu zählen das etwas ältere SAML2 genauso wie der aktuelle quasi Standard OpenID Connect alias OIDC.

Docker

Die Verwendung von KeyCloak mit Docker ist sehr einfach. Wie man es von Docker gewohnt ist, wird keine weitere Installation benötigt.

docker run -d -p 9001:8080 -e DB_VENDOR="h2" -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak jboss/keycloak    

Hier wird die interne in Memory Datenbank H2 explizit verwendet und die Anwendung auf dem Port 9001 exposed. Als Benutzername und Passwort wird admin festgelegt.

Docker mit Datenbank auf dem Host verwenden

KeyCloak kann mit verschiedenen Datenbanken betrieben werden. Diese können mit dem Docker Container in einem isolierten Netzwerk zusammen mit KeyCloak betrieben werden. Manchmal ist aber auch schon eine Datenbank auf dem Host vorhanden und diese soll auch benutzt werden. In diesem Beispiel möchte ich den KeyCloak mit einer auf dem Host laufenden MariaDB verwenden.

Vor dem ersten Start

Wichtig ist das beim Anlegen des Schemas auf latin2 geachtet wird. Muss vor dem Starten des Containers erfolgen, sonst erhält man beim Starten des Containers Fehlrermeldungen, dass die Rowsize nicht ausreiched sei. KeyCloak benötigt die 4096 Zeichen, weil hier die Key abgespeichert werden.

Den Host bekannt machen

Mit der –add-host Anweisung wird in dem Container ein Eintrag in der /etc/hosts Datei erzeugt. Hier also unser Hostname des Hosts und dessen statische IP Adresse. In diesem Fall hat der Host die statische Adresse 192.168.2.101. Sind Syntax ist hierbei Hostname:IP Adresse.

docker run --add-host=manjaro-server:192.168.2.101 --rm -d -p 9001:8080 -e DB_VENDOR=mariadb -e DB_ADDR=manjaro-server -e DB_DATABASE=keycloak -e DB_USER=root -e DB_PASSWORD=spfau -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak jboss/keycloak

Hinter NginX als Reverse Proxy mit SSL

Für die SSL Verschlüsselung kommt NginX als Reverse Proxy zum Einsatz. Die Konfiguration ist pretty straight forward.

Einstellungen in der NginX Konfiguration

# HTTP -> HTTPS
server {
    listen 80;
    server_name keycloak.xxx.org;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name keycloak.xxx.org;

    # SSL
    include /etc/nginx/ssl/*.xxx.org;

    # logging
    error_log /var/log/nginx/keycloak_error.log debug;
    access_log /var/log/nginx/keycloak_access.log;

    #
    # Serve KeyCloak
    #
    location / 
    {
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        # static IP address of KeyCloak host
        proxy_pass              http://192.168.2.101:9001/;

        proxy_read_timeout      600s;
        proxy_send_timeout      600s;
    }
}

Anpassung des KeyCloak Docker Containers

Es muss das Proxy Address Forwarding aktiviert werden. Dazu wird in dem Container mit der JBoss-Cli die Einstellung geändert. Diese kann von außen mit Docker exec aufgerufen werden. Nun noch den Container neu starten, dann sollte der KeyCloak mit SSL über den Reverse Proxy abgesichert sein.

docker exec keycloak /opt/jboss/keycloak/bin/jboss-cli.sh --connect "/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding, value=true)"

Was ist JIB?

Das Google Container Tool JIB ist ein Werkzeug um einfach aus einer Anwendung ein Docker Image zu erzeugen. Docker Images sind im Prinzip auch nur Archive, so dass man diese auch ohne Docker bauen kann. Und genau tut das Tool JIB von Google. Es gibt jeweils ein Plugin für Maven und für Gradle. In diesem Beispiel konzentriere ich mich auf die Gradle Variante von JIB.

Setup

Für das JIB Plugin muss Gradle in mindestes der Version 4.6 vorhanden sein. Die aktuelle Version 1.0.2 wird wie gewohnt eingefügt.

plugins {
  id 'com.google.cloud.tools.jib' version '1.0.2'
}

Der Erste Build

Ein beherzigtes gradle jib erstellt uns ein Docker Image der Anwendung.

gradle jib

Den Default Server ändern

Benutzt eine Anwendung den TomCat, dann muss man das Basis Image ändern. Der Default nimmt JIB eine auf Jetty basierendes Distroless Image. Hier nun ein Beispiel für die Verwendung von TomCat 8.5, ein auf alpine basieredes Image.

jib {
  from.image = 'tomcat:8.5-jre8-alpine'

  // ROOT muss angepasst werden
  container.appRoot = '/usr/local/tomcat/webapps/ROOT'
}

Alternative

Mit Source 2 Image (S2I) von OpenShift steht hierzu auch eine Alternative zur Verfügung. Diese habe ich aber noch nicht evaluiert. Die Quellen findet man hier: https://github.com/openshift/source-to-image