Blog

Alle Beiträge mit dem Tag Docker

HH.Security bei Holisticon – Security im Umfeld von Docker und Kubernetes

Am Donnerstag sind wir Gastgeber des HH.Security-MeetUps. Diesmal geht es um Security im Umfeld von Docker und Kubernetes. Es handelt sich um einen Erfahrungsbericht, an welchen Stellen Probleme auftreten können und wie man sie gegebenenfalls behebt – also sprechen wir über Themen wie Non-Root-User bei Dockerimages, Angriffsvektoren in Kubernetesumgebungen usw. Es wird also spannend und praxisnah.

Wir freuen uns über rege Teilnahme.

Docker Logo

Docker at the next level

Heutzutage schreiben immer noch viele Entwickler Dockerfiles mit der Hand und respektieren dabei vielleicht sogar bestimmte Best Practices oder Ratschläge zur Lösung von Problemen, wie z.B. das PID-1-Problem. Es scheint, als würde die Geschichte sich wiederholen: In der Vergangenheit hat jeder in Handarbeit Server und virtuelle Maschinen hergestellt. Und jetzt machen wir das Gleiche mit Containern.
Abgesehen von diesen Laufzeitproblemen ist die Handhabung von Dockerfiles wirklich albern. Warum nicht mit Provisionierungswerkzeugen? Hier kommt also der Packer von HashiCorp ins Spiel.

Inzwischen weiß jeder, was Docker ist: alles wird in Container verpackt. Immer mehr Tools tauchen auf, um Probleme zu lösen, die sich aus diesen Änderungen ergeben.

Schauen wir uns die Technologien an, die man heutzutage häufig zum Bauen von Docker-Images verwendet:

Packer

  • Der Packer wird verwendet, um ein Image aus einem Base Image zu erstellen, grundlegende Einstellungen durchzuführen und das endgültige Image zu speichern (Commit)
  • Wir nutzen Provisioner und Packer Templates, um die eigentliche Arbeit zur Erstellung des endgültigen Images zu erledigen
  • Ansible wird zur Provisioning genutzt

Ansible

  • Ansible führt Playbooks auf localhost (im Docker-Container) aus.
  • Ansible muss auf dem Image installiert sein, das die Basis für das eigene Image wird
  • Packer lädt den Inhalt des Ansible Playbooks in die Docker-Container-Instanz hoch und führt es lokal aus

Normalerweise konfigurieren wir Docker-Images, indem wir mehrere RUN-Befehle hinzufügen, z.B.

# Install PanDoc
RUN yum -y install epel-release && yum -y install pandoc

# chrome and xvfb
RUN cd /tmp/setup && \
  curl https://intoli.com/install-google-chrome.sh | bash && \
  yum install -y xorg-x11-server-Xvfb

# Install oc and jq
RUN yum -y install epel-release && yum -y install jq

# Install headless Java
RUN yum install -y centos-release-scl-rh && \
    INSTALL_PKGS="git java-1.8.0-openjdk-headless rsync" && \
    yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \
    mkdir -p /home/jenkins && mkdir -p /var/lib/origin \
    chown -R 1001:0 /home/jenkins && \
    ...
    unlink /usr/bin/java && \
    ...

Anstelle einer riesigen Verkettung von RUN-Befehlen können wir mit Ansible Tasks und Rollen ausführen, um das Docker-Image zu konfigurieren. Um direkt einen Schritt weiter zu gehen, verwenden wir den Ansible Provisioner in Packer. Wir benötigen dazu einige Dateien als Grundgerüst:

  • build.json
  • ansible.cfg
  • ansible/playbook

Die build.json besteht dabei aus drei Hauptbestandteilen:

{
    "builders": [{
        "type": "docker",
        "image": "jenkinsci/ssh-slave:latest",
        "commit": true,
        "changes": [
            "VOLUME /data",
            "WORKDIR /data",
            "EXPOSE 6379",
            "ENTRYPOINT [\"docker-entrypoint.sh\"]",
            "CMD [\"redis-server\"]"
        ]
    }],
    "provisioners":[{
        "type": "ansible",
        "user": "root",
        "playbook_file": "ansible/playbook.yml",
        "extra_arguments": [ "-v" ]
      }
    ],
    "post-processors": [[ {
        "type": "docker-tag",
        "repository": "hypery2k/packer-openshift-demo",
        "tag": "latest"
    } ]]
}
  • Der Builder beschreibt das Basis-Image und grundlegende Anpassungen wie Volumes und Entrypoints
  • Der Provisioner beschreibt die Ansible-Konfiguration
  • Im Post-Processor wird der Docker-Tag für das Image festgelegt.

Die Hauptarbeit erledigt dabei aber das Ansible Playbook:

---
- name: Prepare | Setup Ansible runtime
  hosts: all
  gather_facts: no
  tasks:
    - name: Boostrap python
      raw: test -e /usr/bin/python || (apt-get -y update && apt-get install -y python-minimal)

- name: Provision
  hosts: all

  tasks:
    - name: Install init system
      apt:
        name: dumb-init
        state: present
    - name: Put runtime programs
      copy:
        src: files/{{ item }}
        dest: /usr/local/bin/{{ item }}
        mode: 0755
        owner: root
        group: root
      with_items:
        - docker-entrypoint.sh

- name: CleanUp | Remove package artificats and Ansible
  hosts: all
  gather_facts: no
  tasks:

    - name: Remove python
      raw: apt-get purge -y python-minimal && apt-get autoremove -y

    - name: Remove apt lists
      raw: rm -rf /var/lib/apt/lists/*

Vor dem Ausführen von Ansible Tasks installieren wir Ansible innerhalb des Docker-Images und entfernen es anschließend wieder.
Im Beispielprojekt besteht die Konfiguration des Images daraus, Dumb-Init (ein minimales Init-System für Docker) hinzuzufügen und das Entrypoint-Shell-Skript zu kopieren.
Um das Docker Image zu erzeugen, wird lediglich ein packer build build.json benötigt:

==> docker: Creating a temporary directory for sharing data…
==> docker: Pulling Docker image: jenkinsci/ssh-slave:latest
 docker: latest: Pulling from jenkinsci/ssh-slave
 docker: Digest: sha256:bcade2596c695978bf62105a16c0cfe24ebbf72bed0c757d1aec691f005df176
 docker: Status: Image is up to date for jenkinsci/ssh-slave:latest
==> docker: Starting docker container…
 docker: Run command: docker run -v /Users/mreinhardt/.packer.d/tmp/packer-docker670624880:/packer-files -d -i -t jenkinsci/ssh-slave:latest /bin/bash
 docker: Container ID: 99f3efab8a74493b7a3b74d2bab42b463556b36487364c6b057622a6cb749b25
==> docker: Using docker communicator to connect: 172.17.0.2
==> docker: Provisioning with Ansible…
==> docker: Executing Ansible: ansible-playbook - extra-vars 
 docker: /var/folders/vp/2rx0yjfd4w12rx5b5y5bcbsc0000gn/T/packer-provisioner-ansible875281827 did not meet script requirements, check plugin documentation if this is unexpected
 docker: ________________________________________
 docker: < PLAY [Prepare | Setup Ansible runtime] >
 docker: - - - - - - - - - - - - - - - - - - - - 
 docker: ________________________
 docker: < TASK [Boostrap python] >
 docker: - - - - - - - - - - - - 
 docker: changed: [default] => {"changed": true, "rc": 0, "stderr": "Warning: Permanently added '[127.0.0.1]:63005' (RSA) to the list of known hosts.\r\nShared connection to 127.0.0.1 closed.\r\n", "stderr_lines": ["Warning: Permanently added '[127.0.0.1]:63005' (RSA) to the list of known hosts.", "Shared connection to 127.0.0.1 closed."], "stdout": "", "stdout_lines": []}
 docker: __________________
 docker: < PLAY [Provision] >
 docker: - - - - - - - - - 
 docker:
 docker: ________________________
 docker: < TASK [Gathering Facts] >
 docker: - - - - - - - - - - - - 
 docker:
 docker: ok: [default]
 docker: ____________________________
 docker: < TASK [Install init system] >
 docker: - - - - - - - - - - - - - - 
 docker:
 docker: changed: [default] => {"cache_update_time": 1552918991, "cache_updated": false, "changed": true, "stderr": "debconf: delaying package …
 docker: _____________________________
 docker: < TASK [Put runtime programs] >
 docker: - - - - - - - - - - - - - - -
 docker: changed: [default] => (item=docker-entrypoint.sh) => {"changed": true, "checksum": "5d210e2bae060e6c5a5e6046f0ad7282c728b767", "dest": "/usr/local/bin/docker-entrypoint.sh", "gid": 0, "group": "root", "item": "docker-entrypoint.sh", "md5sum": "a622f4b8bc51e2e2cc90f5b800f767ad", "mode": "0755", "owner": "root", "size": 91, "src": "/tmp//ansible/ansible-tmp-1552918998.39–278279285350933/source", "state": "file", "uid": 0}
 docker: ________________________________________________________
 docker: < PLAY [CleanUp | Remove package artificats and Ansible] >
 docker: - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 docker: ______________________
 docker: < TASK [Remove python] >
…
 docker: - - - - - - - - - - - 
 docker: < TASK [Remove apt lists] >
 docker: - - - - - - - - - - - - -
 docker: changed: [default] => {"changed": true, "rc": 0, "stderr": "Shared connection to 127.0.0.1 closed.\r\n", "stderr_lines": ["Shared connection to 127.0.0.1 closed."], "stdout": "", "stdout_lines": []}
 docker: ____________
 docker: < PLAY RECAP >
 docker: - - - - - - 
 docker: default : ok=6 changed=5 unreachable=0 failed=0
 docker:
==> docker: Committing the container
 docker: Image ID: sha256:0a585c2d3f24283ab3620fa39aebad4d570f2e8748549559d78c4e006b9c3db2
==> docker: Killing the container: 99f3efab8a74493b7a3b74d2bab42b463556b36487364c6b057622a6cb749b25
==> docker: Running post-processor: docker-tag
 docker (docker-tag): Tagging image: sha256:0a585c2d3f24283ab3620fa39aebad4d570f2e8748549559d78c4e006b9c3db2
 docker (docker-tag): Repository: holisticonag/packer-demo:latest
Build 'docker' finished.

In diesem einfachen Beispiel ist der Packer ein totaler Overkill, dennoch zeigen sich die prinzipiellen Vorteile:

  • Wir können bestehende Playbooks wiederverwenden oder ganz einfach eine virtuelle Maschine anstelle eines Docker-Images erstellen
  • Die Wartung ist erleichtert, weil wir lesbare Install Tasks erstellen können, anstatt nur darauf zu achten, wenige Docker-Layer zu erstellen
  • Durch die Verwendung von Ansible Playbook ist es erweiterbar, da wir jetzt viele bestehende Ansible Rollen verwenden können

Dennoch sollte man bedenken, dass ggf. zwei neue Werkzeuge erlernt werden müssen: Ansible und Packer.
Der vollständige Code ist auf GitHub verfügbar.

In einem weiteren Artikel werde ich mehr auf die Sicherheitsaspekte von Docker-Images eingehen.


weiterlesen

JavaSPEKTRUM 3/2018: Save im Safe

In der Ausgabe 3/2018 des JavaSPEKTRUM erscheint ein Artikel von mir zum Thema Sicheres Configuration Management mit Hashi Vault: Gerade bei Docker stellt sich das als gar nicht so einfach heraus. Die meisten Vorlagen steuern das Verhalten über Umgebungsvariablen, auch Zugangsdaten. Hier bieten sich mit HashCorp Vault und Consul im Docker Container-Umfeld sehr interessante Möglichkeiten, Einstellungen und weitere schützenswerte Werte sicher abzulegen.
Vor dem Hintergrund immer kürzerer Releases (Continuous Delivery und Integration) wird häufig die Sicherheit vernachlässigt. Penetrationstests sind dabei als Sicherheitsmaßnahmen nicht ausreichend. Durch die Schnelligkeit und Häufigkeit von Deployments steht gerade die IT-Sicherheit vor neuen Herausforderungen, und es sind – ähnlich wie bei der DevOps-Intitiative – weitere Maßnahmen nötig, um die IT-Sicherheit und die Entwicklung enger zu verzahnen. In dem Artikel gehe ich auch auf die Integration mit Jenkins und Spring ein.
Das aktuelle Heft ist im Fachhandel und online verfügbar.

JavaSPEKTRUM 5/2016: Continuous Delivery ohne Kopfschmerzen

In der Oktober/November-Ausgabe des JavaSPEKTRUMs ist ein Artikel von Holisticon-Mitarbeiter Martin Reinhardt über Continuous Delivery erschienen, der sich mit immer kleineren Komponenten im IoT und Microservices Umfeld befasst. Durch den verstärkten Trend zur inkrementell-iterativen Softwareentwicklung in vielen Unternehmen müssen auf einmal viele Zwischenstände einer Software qualitätsgesichert werden. Diese Kette aus Entwicklung, Qualitätssicherung und Auslieferung wird durch die engen Zeitfenster für Tests auf eine harte Probe gestellt. Die beiden aktuellen Trendthemen Internet of Things (IoT) und Microservices verstärken diesen Trend nochmals, weil dadurch zusätzliche Artefakte erstellt und weiterentwickelt werden.

Bei Umsetzung innerhalb von Jenkins sind dabei verschiedene technische und konzeptionelle Vorbereitungen zu treffen, um eine Continuous Delivery (CD) umzusetzen, die auch sicherstellt, dass die einzelnen Puzzleteile der Software zusammenpassen.

In Java helfen einen viele Bibliotheken kontinuierliche Änderungen umzusetzen, z.B. Flyway im Rahmen von der Datenbank-Migration. Außerdem kann man mit dem Pipeline-Plugin von Jenkins Build-Skripte in einer DSL beschreiben und mitversionieren. Um einzelne Module/Services integriert zu testen bietet sich Docker in Verbindung mit Serenity zur Oberflächen-Steuerung an. Dieser Artikel stellt das Vorgehen an einem Github-Projekt vor und steht auch als Beispiel-Projekt in Jenkins aufrufbar.

Die Ausgabe 5/2016 ist seit dem 16. September am Kiosk oder online verfügbar.

Rock CI mit Docker und Jenkins 2

Für Jenkins liegt aktuell die finale Version 2.0 vor, und es hat sich einiges getan: Die neue Version kommt mit einer höheren Zugriffssicherheit, einer verbesserten Plug-in-Auswahl sowie einem überarbeiteten Job-Konfigurationsformular daher. Letzteres bietet bei der Anzeige komplexer Formulare nun eine visuell klarere Struktur; zudem wurden dem Formular mehrere Registerkarten hinzugefügt, um die Navigation zu beschleunigen.
Auch der Weiterentwicklung der Plug-in-Auswahl des Setup-Dialogs haben sich die Jenkins-Macher gewidmet. Ab sofort werden das Git-Plug-in und weitere populäre Erweiterungen  standardmäßig mitinstalliert. Im Hinblick auf das Pipeline-Plug-in wurden die ergänzende Module Pipeline Stage View und GitHub Organization Folder einbezogen. Das Plug-in Pipeline Stage View erlaubt einen schnellen Überblick über die Continuous-Delivery-Pipeline, während GitHub Organization Folder automatisch nach GitHub-Repositorien mit Pipeline-Definitionen sucht und Jobs für diese einrichtet. Dabei kann das Build-Skript für den Job im Versionskontrollsystems auch mit hinterlegt werden und es gibt sehr gute Unterstützung für mehrere Branches, ohne dass separate Jobs angelegt werden müssen.
weiterlesen