Moby: Docker Network contourne le pare-feu, aucune option à désactiver

Créé le 14 avr. 2016  ·  114Commentaires  ·  Source: moby/moby

Sortie de docker version :

Client:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 15:54:52 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 15:54:52 2016
 OS/Arch:      linux/amd64

Sortie de docker info :

Containers: 14
 Running: 5
 Paused: 0
 Stopped: 9
Images: 152
Server Version: 1.10.3
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 204
 Dirperm1 Supported: false
Execution Driver: native-0.2
Logging Driver: json-file
Plugins: 
 Volume: local
 Network: bridge null host
Kernel Version: 3.13.0-58-generic
Operating System: Ubuntu 14.04.4 LTS
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 7.793 GiB
Name: brm-pheonix-dev
ID: Y6Z4:6D53:RFOL:Z3CM:P7ZK:H6HL:RLV5:JT73:LZMC:DTBD:7ILK:2RS5
Username: benjamenmeyer
Registry: https://index.docker.io/v1/

Détails supplémentaires sur l'environnement (AWS, VirtualBox, physique, etc.) :
Rackspace Cloud Server, Ubuntu 14.04, mais cela ne devrait pas vraiment avoir d'importance

Étapes pour reproduire le problème :

  1. Configurer le système avec un pare-feu verrouillé
  2. Créer un ensemble de conteneurs Docker avec des ports exposés
  3. Vérifiez le pare-feu ; docker utilisera "n'importe où" comme source, de sorte que tous les conteneurs sont exposés au public.

Décrivez les résultats que vous avez reçus :
root@brm-pheonix-dev :~/rse# iptables --list DOCKER
Chaîne DOCKER (1 références)
cible prot opt ​​source destination
ACCEPTER tcp -- n'importe où 172.17.0.2 tcp dpt:6379

Décrivez les résultats que vous attendiez :
root@brm-pheonix-dev :~/rse# iptables --list DOCKER
Chaîne DOCKER (1 références)
cible prot opt ​​source destination
ACCEPTER TCP -- 127.0.0.0/24 172.17.0.2 TCP dpt:6379
ACCEPTER TCP -- 172.16.0.0/16 172.17.0.2 TCP dpt:6379

Informations supplémentaires que vous jugez importantes (par exemple, le problème n'arrive qu'occasionnellement) :

Par défaut, docker manipule le pare-feu d'une manière qui brise la sécurité - il permet à tout le trafic de tous les périphériques réseau d'accéder aux ports exposés sur les conteneurs. Considérons un site qui a 2 conteneurs : le conteneur A expose 443 exécutant Nginx et le conteneur B exécute une API sur le port 8000. Il est souhaitable d'ouvrir le conteneur A au public pour utilisation, mais masquez entièrement le conteneur B afin qu'il ne puisse parler qu'à localhost. (pour les tests par l'utilisateur) et le réseau Docker (pour parler au conteneur A). Il peut également être souhaitable, à des fins de test, que le conteneur C soit une base de données utilisée par le conteneur B avec le même type de restrictions.

J'ai trouvé cela à cause de la surveillance des journaux sur un service que je pensais n'être pas ouvert au public. Après avoir trouvé des entrées de journal provenant de sources essayant de s'introduire, j'ai vérifié les règles de pare-feu et constaté qu'il n'y avait aucune limite sur les adresses ou les interfaces sources. J'utilise UFW et n'autorise SSH que sur cette boîte particulière, et je préférerais que cela reste ainsi. Cela peut avoir un impact considérable sur l'utilisation de conteneurs Docker pour déployer des services et entraîner des problèmes de sécurité potentiels si les utilisateurs ne font pas attention.

La meilleure pratique de sécurité serait de limiter par défaut la mise en réseau pour qu'elle fonctionne comme l'exemple d'effet souhaité ci-dessus, puis de permettre à l'utilisateur d'ajouter le pare-feu approprié, etc. Je sais que pour des raisons héritées, ce n'est pas probable car cela casserait beaucoup de choses à jour; donc au moins avoir une option pour activer ce qui précède qui peut être activé maintenant serait une bonne première étape, et peut-être plus tard, après de nombreux avertissements, en faire le comportement par défaut. En supposant que le comportement par défaut est sécurisé, avoir des fonctionnalités pour gérer cela (pare-feu-> activer le port public, ip) dans le docker-compose yml serait un excellent moyen de faire visiblement savoir ce qui se passe.

J'ai trouvé l'option --iptables=false, cependant, je ne veux pas avoir à définir toutes les règles moi-même. La seule chose à laquelle je m'oppose est le réglage de la source pour les règles.

Bien que je ne l'aie pas vérifié, je soupçonne que tous les pare-feu pris en charge par docker auront le même problème.

arenetworking versio1.10

Commentaire le plus utile

Le problème que Ben met en lumière est réel et surprenant (une mauvaise combinaison). De nombreux administrateurs, comme moi, utilisent le pare-feu ufw éprouvé. Docker est en train de contourner ufw et de modifier les règles iptables de manière à 1) faire en sorte que ufw signale de manière erronée l'état actuel des règles de filtrage des paquets, et 2) expose des services apparemment privés au réseau public. Pour que docker reste dans les bonnes grâces de la communauté sysadmin, une autre approche doit être imaginée. À l'heure actuelle, il existe de nombreux administrateurs qui, comme Ben et moi-même, ont ouvert par inadvertance des ports vers l'Internet au sens large. Contrairement à Ben et moi, ils ne l'ont pas encore compris.

Tous les 114 commentaires

Remarque: j'ai remarqué dans https://github.com/docker/docker/blob/master/vendor/src/github.com/docker/libnetwork/iptables/iptables.go qu'il n'y a même pas d'option existante pour définir la source , il utilise donc simplement les valeurs par défaut d'iptables pour l'ip/l'appareil source.

Ce n'est pas tout à fait #14041 car ce problème parle de ports exposés. L'exposition des ports vise à les rendre accessibles au public, car c'est ainsi que vous exposez les services au monde extérieur. Si vous travaillez dans un environnement de développement, vous pouvez soit désactiver l'accès aux ports depuis l'extérieur de votre ordinateur avec un pare-feu hôte, soit simplement ne pas exposer les ports et accéder aux services directement, ou à partir d'autres conteneurs sur le même réseau.

Je vous recommande d'utiliser les nouvelles fonctionnalités de mise en réseau de Docker pour configurer des réseaux privés pour les services que vous ne souhaitez pas du tout exposés, voir https://docs.docker.com/engine/userguide/networking/

C'est ce à quoi j'ai pensé en premier; mais était un peu confus, car _exposer_ un port ( EXPOSE ), ne fait rien, mais _publier_ un port ( -p / -P ) l'expose en fait sur le hôte.

Si vous parlez réellement de _publication_, alors c'est comme prévu ;

Dans votre exemple, les conteneurs B et C ne doivent pas publier leurs ports, et le conteneur A peut communiquer avec eux via le réseau Docker, par ex.

docker network create mynet

docker run -d --net=mynet --name=api api-image
docker run -d --net=mynet --name=db database-image
docker run -d --net=mynet --name=web -p 443:443 nginx

Cela ne publie que le conteneur "web" sur l'hôte, le conteneur web peut accéder aux conteneurs "API" et "base de données" via leur nom (c'est-à-dire http://api :80/ et db:3306 (en supposant MySQL))

@justincormack donc je ne pense pas que l'utilisation d'un réseau privé résolve le problème. Dans mon cas, j'utilise un réseau privé entre les conteneurs, et ils sont toujours exposés publiquement car le pare-feu de l'hôte n'est pas configuré pour limiter l'exposition au réseau privé.

@thaJeztah le problème se résume toujours à la prise en charge du pare-feu - il n'y a pas de prise en charge du pare-feu dans docker pour le limiter à un réseau spécifique. Vous pouvez probablement toujours accéder à ces conteneurs à partir d'un autre système car le pare-feu n'empêchera pas les autres systèmes d'accéder au port de l'hôte.

Maintenant, je l'exécute via docker-compose; Cependant, ce n'est pas entièrement un problème de composition de docker puisque la fonctionnalité libnetwork n'a pas la capacité de limiter le réseau dans les règles de pare-feu - les règles iptables n'ont pas de spécification de source, donc quelle que soit la façon dont on configure le réseau tant que l'on s'appuie sur docker pour créer le règles de pare-feu (ce qui devrait être le cas car il est plus probable qu'elles soient correctes), cela devient alors un problème. Considérez ce qui suit dans un fichier docker-compose.yml :

nginx:
    build: ./docker/nginx/.
    ports:
        - "127.0.0.1:8080:80"
        - "127.0.0.1:443:443"
    environment:
        DESTINATION_HOST: repose
    links:
        - repose
repose:
    build: ./docker/repose/.
    ports:
        - "127.0.0.1:80:8080"
    environment:
        DESTINATION_HOST: phoenix
        DESTINATION_PORT: 8888
    links:
        - phoenix
curryproxy:
    build: ./docker/curryproxy/.
    ports:
        - "127.0.0.1:8081:8081"
    external_links:
        - rse_rse_1
        - rse_rse_2
        - rse_rse_3
phoenix:
    build: .
    ports:
        - '127.0.0.1:88:8888'
    links:
        - curryproxy:curry
    external_links:
        - rse_rse_1:rse
        - rse_rse_2
        - rse_rse_3
        - rse_cache_1:cache
    volumes:
        - .:/home/phoenix

Ce qui précède est un extrait d'un de mes projets. Bien que je veuille pouvoir tous les tester localement à partir de mon hôte, je ne veux pas que quelqu'un d'autre puisse accéder à autre chose qu'à l'instance nginx.

Je ne sais pas comment cela se traduit dans votre nomenclature... il se peut que cela fasse partie de l'aspect "publication", et la capacité de publication doit être étendue pour faire ce que je dis.

Si c'est par conception, alors c'est un mauvais modèle de sécurité car vous exposez maintenant tous les développeurs à des risques extrêmes lorsqu'ils sont sur des réseaux inconnus (par exemple en voyage).

Comme je l'ai dit, je ne m'attends pas à ce que la valeur par défaut change immédiatement, mais avoir l'option serait une bonne première étape.

Je suis un peu confus alors, pouvez-vous donner quelques exemples externes de ce à quoi vous pouvez vous connecter ? Les services principaux seront (par défaut) sur le réseau 172.17.0.0/16 , auquel vous ne pourrez pas accéder en externe, je ne pense pas en premier lieu car vous n'aurez pas de route vers celle définie à partir d'un hôte externe.

Il y a un problème potentiel si votre IP externe est également une IP privée que le trafic ne sera pas abandonné qui est acheminé vers les réseaux internes (alors qu'il devrait être du public au privé) - est-ce le problème ?

@justincormack donc je configure principalement un proxy approprié afin que certains services ne puissent être touchés que via le proxy (nginx - terminaison ssl), qui filtre ensuite via un proxy d'authentification (repose), et enfin vers un autre service (phoenix). Je m'en fiche si tous sont liés à l'interface 0.0.0.0 ; mais je veux seulement que nginx soit accessible de l'extérieur (ou à tout le moins la partie repos si je n'avais pas nginx en place ici). Une solution simple, par exemple, serait de ne pas avoir à définir "127.0.0.1" dans la configuration, mais d'avoir une section de pare-feu où il est facile de spécifier que d'autoriser le pare-feu avec une configuration de base uniquement du réseau docker et local interfaces hôte (boucle) activées pour parler - quelque chose comme :

firewall:
    external:
        ports:
            - 80
            - 443

Maintenant, la situation peut être quelque peu atténuée en limitant le mappage réseau sur le _host_ à 127.0.0.1 au lieu de la carte par défaut 0.0.0.0. Notez que c'est ce qui l'atténue vraiment, car sinon le pont transmettra le port hôte au réseau docker.

Et oui, j'ai vérifié que cette limitation fonctionne ; cependant, il laisse toujours des vulnérabilités potentielles en place et les règles de pare-feu ne correspondent pas à ce qui est réellement fait.

Comme autre exemple, il y a peu de temps, il y avait une vulnérabilité du noyau Linux (ayant du mal à la trouver pour le moment) qui était liée aux ports marqués dans IPtables comme étant ouverts pour être utilisés par des applications, mais n'étant pas réellement connectés à une application. - par exemple, être sur un port hôte local mais pas sur un port IP public. Cela configure potentiellement cela, et il serait préférable de limiter les règles IPtables aux réseaux attendus au lieu de les laisser ouverts pour se connecter à partir de n'importe où. Comme je l'ai dit, au moins avoir la possibilité de spécifier. Ils ont probablement résolu ce problème particulier, mais pourquoi laisser la possibilité ouverte ?

IOW, tout est une question de sécurité.

@BenjamenMeyer si vous ne voulez pas que les autres services soient accessibles, pourquoi publiez-vous leurs ports du tout ? c'est- "127.0.0.1:8081:8081" dire que

Un problème que j'ai lié à celui-ci est que je voudrais publier des ports, mais n'autoriser que certaines adresses IP à y accéder.

Par exemple, j'exécute un environnement Jenkins dans quelques conteneurs. Le nœud maître est "publié", mais je dois faire des règles iptables assez alambiquées pour le verrouiller afin que seuls les 2 bureaux que nous avons puissent y accéder.

Existe-t-il un moyen de contourner ce problème actuellement intégré à Docker ? Ou au moins une pratique recommandée? J'ai vu dans la documentation comment vous pouvez restreindre l'accès à 1 adresse IP ; mais pas plusieurs. L'autre problème avec ceci est que si vous avez un serveur qui a déjà une configuration iptables, vous pourriez réinitialiser toutes les règles avant d'appliquer vos règles (d'où les règles alambiquées que j'ai dû configurer).

J'ai un problème similaire à celui signalé par @SeerUK. Il y a une violation flagrante des attentes lorsque les règles de pare-feu préexistantes ne s'appliquent pas aux ports de conteneur publiés. Le comportement souhaité est le suivant (pour moi du moins)

  1. La tentative de connexion de l'utilisateur est filtrée en fonction des configurations INPUT, etc.
  2. Le transfert de trafic se produit ensuite comme d'habitude en fonction des règles FORWARD ajoutées par docker

Existe-t-il un moyen succinct d'y parvenir dans iptables, ou ne permet-il pas facilement une telle construction. Je suis particulièrement limité dans ma connaissance d'iptables, alors soyez indulgents avec moi. J'ai récemment acquis des connaissances à ce sujet en essayant de comprendre les interactions de docker avec lui.

Ce à quoi j'ai eu recours pour le moment, puisque j'exécute en fait ces conteneurs sur un serveur dédié assez puissant, j'ai configuré une machine virtuelle KVM exécutant Docker, puis j'utilise des règles iptables plus standard pour restreindre l'accès depuis l'hôte . La machine virtuelle a sa propre interface réseau qui n'est accessible qu'à partir du serveur, je dois donc ajouter des règles pour autoriser explicitement l'accès aux ports dans iptables sur l'hôte. J'ai perdu un peu de performance, mais pas beaucoup.

@thaJeztah Je veux pouvoir y accéder à partir du système local et le tester facilement. Par exemple, configurer une API HTTP RESTful qui a un point de terminaison Health et être capable d'exécuter de manière fiable curl en utilisant localhost (je dois documenter cela pour les autres et avoir des adresses IP qui changent n'est pas fiable ). Dans la plupart des cas, pour mon environnement de développement, je souhaite uniquement que les conteneurs communiquent entre eux, mais je souhaite également pouvoir y accéder à partir de l'hôte.

Pour le cas de @SeerUK , pouvoir définir un bloc IP (5.5.0.0/16 - un paramètre valide pour une adresse source dans les règles iptables) serait une très bonne chose. IPtables a déjà la capacité de faire la limitation, mais docker n'en profite pas.

@thaJeztah J'ai défini "127.0.0.1:8081:8081" explicitement pour le garder hors du réseau externe; J'avais trouvé des journaux dans mes conteneurs docker provenant de personnes essayant de pénétrer dans les conteneurs via les ports exposés.

Mon travail en ce moment consiste à désactiver les conteneurs Docker avant de partir pour la journée, car je ne peux pas garantir que l'environnement que je veux être externe est réellement externe, ou que l'environnement est correctement limité à des fins de sécurité.

@BenjamenMeyer, une façon de procéder consiste à exécuter ces tests dans un conteneur, par exemple

docker run --net -it --rm --net=mynetwork healthchecker 

Le problème que Ben met en lumière est réel et surprenant (une mauvaise combinaison). De nombreux administrateurs, comme moi, utilisent le pare-feu ufw éprouvé. Docker est en train de contourner ufw et de modifier les règles iptables de manière à 1) faire en sorte que ufw signale de manière erronée l'état actuel des règles de filtrage des paquets, et 2) expose des services apparemment privés au réseau public. Pour que docker reste dans les bonnes grâces de la communauté sysadmin, une autre approche doit être imaginée. À l'heure actuelle, il existe de nombreux administrateurs qui, comme Ben et moi-même, ont ouvert par inadvertance des ports vers l'Internet au sens large. Contrairement à Ben et moi, ils ne l'ont pas encore compris.

@thaJeztah qui suppose que je le fais via la ligne de commande et que je n'utilise pas un autre outil où je n'ai qu'à définir une adresse IP.

Par exemple, je travaille sur une API. J'ai un outil que je peux utiliser pour travailler avec cette API en production pour la prendre en charge ; pour le développement de l'outil et de l'API, je souhaite simplement pointer l'outil vers l'API dockerisée. L'outil ne connaît rien à Docker, et il ne devrait pas le faire non plus. Et je ne veux pas nécessairement mettre l'outil dans docker juste pour l'utiliser - le pointer sur un port exposé uniquement à l'hôte local devrait être suffisant.

@jcheroske Je suis d'accord, mais je ne sais pas s'il existe une bonne solution à _ce_ aspect. Pour cela, ufw doit probablement être rendu plus intelligent pour pouvoir rechercher et signaler des règles qu'il n'a pas participé à la création. Il existe de nombreux logiciels qui peuvent ajuster les règles iptables d'une manière que ufw (ou AFAIK firewalld, etc.) ne connaîtra pas. Il n'y a pas vraiment de solution simple pour résoudre ce problème non plus.

Cela dit, ce serait bien si Docker pouvait s'intégrer à ceux-ci pour vider les fichiers de configuration appropriés pour pouvoir les activer/désactiver, ou s'intégrer à ces outils de telle sorte qu'il s'y accroche et vide les informations de manière appropriée, cependant, étant donné il existe de meilleures solutions, je ne pense pas que cet aspect soit vraiment résolu. Ici, il s'agit plutôt de limiter la portée des règles iptables qui sont générées pour au moins minimiser les impacts potentiels en permettant la spécification de la source (lo, eth0, 127.0.0.0/24, etc).

Si vous êtes prêt à le faire, l'utilisation d'iptables rend cela totalement possible.

Voici un exemple simplifié de la façon dont vous pouvez l'utiliser : https://gist.github.com/SeerUK/b583cc6f048270e0ddc0105e4b36e480

Vous pouvez voir que tout en bas, 1.2.3.4 a explicitement accès au port 8000 (qui est exposé par Docker), puis tout le reste sur ce port est supprimé. La chaîne PRE_DOCKER est insérée avant la chaîne DOCKER afin qu'elle soit touchée en premier, ce qui signifie que DROP empêche les requêtes bloquées d'atteindre la chaîne DOCKER.

C'est un peu ennuyeux que Docker n'ait pas cette fonctionnalité intégrée, mais il est possible de la contourner dès maintenant.

Une autre alternative serait d'utiliser un pare-feu externe. Certains endroits comme AWS et Scaleway proposent des choses comme des groupes de sécurité où vous pouvez gérer l'accès à vos boîtiers depuis l'extérieur, à partir de là, chaque port se comporte de la même manière.

Je n'ai jamais réussi à comprendre comment faire fonctionner cela avec UFW. Bien que pour l'instant, je suis content d'utiliser iptables comme solution. Cela semble très bien fonctionner pour moi jusqu'à présent.

Évidemment, ce n'est pas vraiment une bonne solution si vous avez déjà construit un ensemble de règles de pare-feu assez compliqué autour d'UFW. Cependant, cela rend l'utilisation de choses comme iptables-persistent assez facile. Vous pouvez également utiliser d'autres moyens d'autoriser l'accès de cette manière qui semblent plus "normaux" dans iptables.

@BenjamenMeyer avez-vous pensé à utiliser un docker network défini par l'utilisateur avec une option de sous-réseau et de plage d'adresses IP et d'attribuer une adresse IP statique aux conteneurs et de les utiliser pour le développement local afin que vous n'ayez pas à dépendre de une IP statique virtuelle telle que 127.0.0.1 ? Cela évitera d'avoir besoin de mapper tous les ports pour les conteneurs privés d'un hôte.

docker network create --subnet=30.1.0.0/16 --ip-range=30.1.0.0/24 mynetwork
docker run --net=mynetwork --ip=30.1.1.1 --name=myservice1 xxxx
docker run --net=mynetwork --ip=30.1.1.2 --name=myservice2 yyyy

Avec cette configuration, myservice2 peut atteindre myservice1 par le nom myservice1 et il n'est même pas nécessaire de dépendre de l'adresse IP statique. De plus, l'hôte peut accéder librement à l'adresse IP statique sans avoir besoin d'un mappage de port.

De plus, avec compose 1.7, vous pouvez spécifier une adresse IP statique pour les conteneurs et spécifier des sous-réseaux et des plages de réseau.

J'ai trouvé une solution de contournement simple.

1) Modifier /etc/default/docker : DOCKER_OPTS="--iptables=false"

2) Ajouter une règle ufw : ufw allow to <private_ip> port <port>

Si simple que cela me fait vraiment me demander pourquoi l'option --iptables=false n'est pas la valeur par défaut. Pourquoi créer une telle situation alors que tout ce que docker a à faire est de dire : "Hé, si vous utilisez un pare-feu, vous allez devoir percer un trou !" Qu'est-ce que je rate?

https://fralef.me/docker-and-iptables.html
http://blog.viktorpetersson.com/post/101707677489/the-dangers-of-ufw-docker

Je n'arrive pas à faire en sorte que docker arrête de modifier iptables pour me sauver la vie. J'ai essayé de mettre à jour /etc/default/docker en vain sur Ubuntu 16.04

@enzeart Essayez /lib/systemd/system/docker.service .

@SeerUK Bénis ton âme

@enzeart pour configurer un démon s'exécutant sur un hôte qui utilise systemd, il est préférable de ne pas modifier le fichier docker.unit lui-même, mais d'utiliser un fichier "drop in". De cette façon, vous ne rencontrerez pas de problèmes lors de la mise à niveau de docker (au cas où il existerait un fichier docker.unit plus récent). Voir https://docs.docker.com/engine/admin/systemd/#custom -docker-daemon-options pour plus d'informations.

Vous pouvez également utiliser un fichier de configuration daemon.json, voir https://docs.docker.com/engine/reference/commandline/daemon/#daemon -configuration-file

@mavenugo Il y a déjà un réseau de dockers en place.

@jcheroske qui fonctionne, mais comme je l'ai noté, cela signifierait que l'_utilisateur final_ (moi) devrait alors s'assurer que toutes les règles iptables étaient correctes, ce qui n'est pas optimal et n'est pas aussi susceptible de se produire que avoir docker faire automatiquement, d'où ce problème.

Salut, s'il te plaît. Je pense que son problème aussi. La chaîne de conteneurs dans Iptables doit suivre les règles principales et ne pas être exposée au monde par défaut.

J'aimerais vraiment que Docker (et docker-compose) aient la possibilité de mettre sur liste blanche ou sur liste noire les adresses IP pouvant accéder à ce port.

Prends pour exemple:

nginx:
    ports:
      - "8000:8000"
    whitelist:
      - 10.6.20.2

Cela impliquerait que seule une adresse IP source de 10.6.20.2 pourrait accéder au port 8000 sur cet hôte.

@StefanPanait J'aime vraiment cette idée. Cela pourrait également fonctionner avec une syntaxe similaire aux volumes et aux listes d'accès/refus, quelque chose comme :

nginx:
  access:
  - "10.0.1.6:allow"
  - "deny"

Il faudrait bien sûr encore permettre des choses comme la communication entre les conteneurs.

La communication inter-conteneurs

Bien que je suppose que la bonne façon de le faire serait de séparer les réseaux docker... je pense néanmoins pouvoir faire quelque chose comme :

nginx: access: - "10.0.1.6:allow" - "webapi:allow" - "database:deny" - "deny"
Pourrait être utile... la question est, est-ce assez utile pour justifier la mise en œuvre à ce degré ? Je ne sais pas.

À l'heure actuelle, j'aimerais voir le problème d'origine résolu, puis des fonctionnalités comme celle-ci peuvent être ajoutées si elles n'ont pas de sens à concevoir dans la résolution de départ (elles pourraient le faire).

pourquoi ne devriez-vous pas pouvoir interdire à un conteneur de lui parler ? Cela pourrait être extrêmement utile pour le débogage

C'est pour docker network disconnect ? Vous pouvez déconnecter un conteneur du réseau pour le débogage et le rattacher avec docker network attach

Pour ceux qui viennent de découvrir qu'une tonne de ports étaient ouverts sur leurs serveurs exposés à Internet, après avoir utilisé UFW, j'ai creusé et creusé et découvert ce qui suit :

Ubuntu 16.04 avec UFW et Docker présente de nouveaux défis. J'ai fait toutes les étapes comme indiqué ici : https://svenv.nl/unixandlinux/dockerufw MAIS je ne pouvais PAS faire fonctionner docker plus UFW le 16.04. En d'autres termes, peu importe ce que je faisais, tous les ports Docker sont devenus globalement exposés à Internet. Jusqu'à ce que je trouve ceci : http://blog.samcater.com/how-to-set-docker-1-12-to-not-interfere-with-iptables-firewalld/
J'ai dû créer le fichier : /etc/docker/daemon.json et mettre ce qui suit dans :

{
"iptables": faux
}

J'ai ensuite émis sudo service docker stop puis sudo service docker start ENFIN, docker suit simplement les règles appropriées dans UFW.

Données supplémentaires : https://chjdev.com/2016/06/08/docker-ufw/

@thaJeztah

pourquoi ne devriez-vous pas pouvoir interdire à un conteneur de lui parler ? Cela pourrait être extrêmement utile pour le débogage
C'est à quoi sert la déconnexion du réseau Docker ? Vous pouvez déconnecter un conteneur du réseau pour le débogage et le rattacher avec docker network attach

Et si l'on souhaitait toujours avoir une connectivité réseau ? Exemple : Tester l'échec de la vérification de l'état du serveur dans le conteneur B pour le conteneur A tout en conservant les services fournis par les conteneurs C, D et E. Il est plus facile d'interdire simplement au conteneur B d'être transféré vers le conteneur A que de fermer l'ensemble du réseau - les conteneurs C peuvent également dépendent de l'accès au conteneur B pour que son bilan de santé réussisse.

Pourtant, cela ne résiste pas au "réglons le problème d'origine".

@gts24 trouvaille intéressante.

À mon humble avis, tout le problème est que Docker, comme littéralement tous les autres programmes, ne devrait pas du tout toucher le pare-feu (iptables ou autre). Lorsque j'installe (par exemple) Apache et que je lui dis d'écouter sur 0.0.0.0:80 c'est ma décision d'ouvrir ou non le port 80 sur le pare-feu, où je peux spécifier les règles que je souhaite.

Au lieu de réinventer les règles de pare-feu sur les fichiers de configuration docker (et/ou composer), toute la fonctionnalité PUBLISH devrait être obsolète et une nouvelle fonctionnalité LISTEN créée pour fonctionner comme tous les autres programmes. Au mieux, docker pourrait créer des services pare-feu

@gcscaglia a mis --iptables=false sur le démon et vous devriez l'obtenir

@thaJeztah Cette fonctionnalité est inutile car Docker n'offre aucune alternative pour obtenir les règles de pare-feu nécessaires (ou des crochets de script de déclenchement plus spécifiques avec les paramètres pertinents) à partir du démon. Il est à peine utilisable si vous avez des conteneurs statiques et que vous ne changez rien à leur sujet (par exemple les ports publiés) mais pour tous les autres cas, vous pouvez l'oublier.

@thaJeztah exactement ce que Taladar a dit.

--iptables=false doit être renommé en --networking=false car même le réseau interne de conteneur à conteneur ne fonctionne pas avec cette fonction désactivée. Une option pour qu'un conteneur écoute sur une combinaison port/interface sans percer les règles du pare-feu entrant (c'est- LISTEN dire --iptables=true .

Avec un tel mode d'écoute, ceux qui veulent que cela "fonctionne" peuvent continuer à utiliser PUBLISH , et ceux qui veulent le contrôle peuvent utiliser LISTEN .

@gcscaglia ok, donc si vous voulez que docker configure les règles de base et gère la mise en réseau conteneur-conteneur mais pas la "publication". Vous pouvez garder --iptables activé, mais _n'utilisez pas_ -p / --publish . Vous devriez pouvoir définir manuellement les règles IPTables pour transférer les ports vers les conteneurs. Le conteneur écoute déjà sur sa propre adresse IP privée et les ports sur lesquels le service de votre conteneur écoute.

@thaJeztah Non, vous ne pouvez pas. Parce que vous n'avez aucune idée qu'il existe même un conteneur qui vient de démarrer et qui a besoin de règles de pare-feu. Vous n'avez aucun moyen de dire à celui qui a utilisé l'API pour le lancer sur quel port hôte il écoute non plus.

Exemple simple. Nous utilisons le conteneur Docker pour exécuter les tâches Jenkins. Ils exposent leur port SSH afin que Docker puisse les contacter. Ils sont détruits dès que le travail est terminé. Docker n'offre aucun moyen de faire fonctionner cela avec --iptables=false car vous n'avez aucun moyen de dire à Jenkins (en utilisant l'API Docker pour lancer le conteneur) le port hôte et vous n'avez même aucun moyen de déclencher un script à configurer les règles de pare-feu nécessaires.

Votre idée ne fonctionne que pour le cas d'utilisation ridiculement simple d'avoir des conteneurs permanents, ne changeant jamais, lancés manuellement. Même un simple --restart=always dans le conteneur s'arrêtera dans cette configuration à moins que le conteneur n'ait une adresse IP statique.

@taladar Je réponds au cas d'utilisation de @gcscaglia , qui a demandé une fonctionnalité où docker ne gère _pas_ les IPTables pour ouvrir les ports, et où ils contrôlent les IPTables; c'est-à-dire qu'aucun conteneur n'est exposé, à moins que cela ne soit fait manuellement. De plus, dans ma réponse, j'ai expliqué de _pas_ utiliser --iptables=false , mais pour le garder activé, mais n'utilisez tout simplement pas la fonctionnalité -p / --publish (qui dit à docker de faire ces ports accessibles via IPTables).

@thaJeztah Bien que vous ayez bien compris mon cas d'utilisation, AFAIK la solution proposée ne fonctionnera pas pour moi pour les mêmes raisons que cela ne fonctionnera pas pour le cas Taladar: Un seul redémarrage de quoi que ce soit et tout à coup, mes règles manuelles doivent être mises à jour. Il n'y a pas de crochets ou de déclencheurs que je peux utiliser pour être averti d'un redémarrage afin que je puisse automatiser une telle mise à jour (sans parler du fait que j'ai réinventé la roue).

Actuellement, la seule solution pour mon cas est d'avoir un autre pare-feu (que le docker ne peut pas toucher) entre l'hôte docker et tous les réseaux externes. Mais si Docker faisait simplement tout ce qu'il fait déjà, sauf ouvrir des ports sur le monde, je pourrais supprimer complètement le deuxième pare-feu.

Je suppose que vous ne pouvez pas tout avoir, mais c'est sûrement frustrant. Pensez-vous que cela vaut la peine d'ouvrir une demande de fonctionnalité sur mon idée LISTEN , ou l'équipe Docker ne serait pas intéressée par une telle fonctionnalité ?

Que feraient les LISTEN ? Il y a EXPOSE , qui vous permet d'annoter sur quels ports le conteneur écoute. Pour les déclencheurs, vous pouvez écouter le docker events .

Je ne dis pas qu'il n'y a pas de place pour l'amélioration ici (je sais que c'est en cours d'examen), je me demande juste à quoi vous vous attendez

Actuellement, comme vous l'avez dit, tous les services exécutés dans un conteneur se lient à l'adresse IP privée du conteneur (si vous n'utilisez pas --net=host ou similaire). C'est bon et souhaitable car une telle isolation entre l'hôte et les conteneurs est exactement le point de vente de Docker.

Mais, actuellement, si je veux qu'une application s'exécutant en dehors de tout conteneur (que ce soit sur l'hôte ou ailleurs sur le réseau) ait accès à un service s'exécutant dans un conteneur, j'ai besoin de moyens pour que ce service écoute sur l'une des interfaces réseau de l'hôte. Pour résoudre ce problème sans exposer les interfaces d'un hôte au conteneur, Docker a créé la fonctionnalité -p / --publish , qui :

  1. Crée des règles iptables pour transférer un port choisi sur l'interface d'un hôte choisi vers un port choisi sur l'adresse IP privée du conteneur (ce que nous attendons tous puisque c'est ce que nous avons demandé)
  2. Dit à iptables d'autoriser n'importe qui de n'importe où dans le monde à accéder à ce port sur l'interface de l'hôte choisi (ce qui n'est pas nécessaire pour que le transfert fonctionne et surprend donc beaucoup d'entre nous)

Ce que je propose, c'est une fonctionnalité (nommée LISTEN ou autre) qui ne fait que "1" et laisse "2" à la discrétion de l'utilisateur, comme tous les autres services/programmes le font habituellement.

Quant à EXPOSE , AFAIK ce ne sont que des métadonnées dans les images Docker afin que le démon sache quoi faire lorsque l'utilisateur spécifie -P (publier tout). Peut-être que je me trompe et que les ports "exposés" peuvent être transférés de manière résistante au redémarrage (sans accès mondial) ?

--hors sujet--

À mon humble avis, l'OP de ce problème demande exactement pourquoi -p fait "2" et comment empêcher Docker de le faire. Alors qu'un paramètre de niveau démon (autre que la désactivation du réseau) pourrait le résoudre, afin de garder les choses rétrocompatibles, le mieux serait une nouvelle fonctionnalité (c'est- LISTEN dire

Alors que de nombreux utilisateurs aiment cet effet "prêt à l'emploi", aucun administrateur système ne s'attend à ce que des programmes autres que leurs iptables / firewalld ouvrent des ports sur le pare-feu, encore moins d'une manière dont leur logiciel de gestion de pare-feu ne rend pas compte.

Je vois qu'il y a trois problèmes majeurs :

  1. La manière de Docker de publier/exposer les ports contourne les règles de pare-feu courantes comme UFW.
  2. Le fait ci-dessus ne semble pas être bien documenté (c'est complètement inattendu car aucun autre service Linux à ma connaissance ne contourne les règles de pare-feu).
  3. Il n'y a pas de moyen simple d'adapter iptables à la manière de Docker de publier/exposer les ports ou les façons dont les gens connaissent ne sont pas faciles à automatiser et aucune n'est documentée.

Peut-être qu'il n'est techniquement pas faisable d'implémenter l'exposition du port à la table FILTER et donc la correction 1 n'est pas possible. À tout le moins, cela devrait recevoir un gros avertissement quelque part (correctif 2), mais idéalement, 3 pourraient être résolus avec de nouvelles options comme celles suggérées par des personnes ici, telles que allow ou deny qui ajouteraient des règles de pare-feu supplémentaires automatiquement pour autoriser ou refuser des IP spécifiques pour les ports exposés/publiés.

Par exemple, allow: "tcp:{your_trusted_ip}" pour un conteneur nommé "elasticsearch" port de publication 9200 pourrait faire quelque chose comme :

iptables -t mangle -N DOCKER-elasticsearch
iptables -t mangle -A DOCKER-elasticsearch -s {your_trusted_ip} -j RETURN
iptables -t mangle -A DOCKER-elasticsearch -j DROP
iptables -t mangle -I PREROUTING -p tcp --dport 9200 -j DOCKER-elasticsearch

J'ai trouvé cela assez utile dans la documentation Docker : https://docs.docker.com/engine/userguide/networking/default_network/container-communication/#communicating -to-the-outside-world

Les règles de transfert de Docker autorisent toutes les IP sources externes par défaut. Pour autoriser uniquement une adresse IP ou un réseau spécifique à accéder aux conteneurs, insérez une règle d'annulation en haut de la chaîne de filtrage DOCKER. Par exemple, pour restreindre l'accès externe de sorte que seule l'IP source 8.8.8.8 puisse accéder aux conteneurs, la règle suivante pourrait être ajoutée :

$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j SUPPRIMER

@jmimico oui j'ai déjà rencontré ça. Comment restreindre l'accès de 2 IP ou plus ?

En quoi Docket ajoute-t-il simplement une option pour exécuter un script shell à n'importe quel endroit où il crée désormais des règles iptables avec toutes les informations internes à Docker transmises au script en tant que paramètres ? Cela permettrait à chacun de créer exactement les règles dont il a besoin. Ajoutez un moyen de déclencher une réexécution des scripts pour les conteneurs actifs que les utilisateurs peuvent appeler après avoir effectué une restauration iptables ou vidé les chaînes pour d'autres raisons et vous avez terminé.

Docker n'a pas besoin de recréer toutes sortes de scénarios de pare-feu prédéfinis, comme le suggèrent certaines personnes ici, qui pourraient être construits au-dessus d'un système comme celui-ci par des ensembles de distribution de scripts hook. Tout au plus, quelque chose comme exposer uniquement sur localhost et exposer globalement (comme Docker le fait maintenant) pourrait avoir du sens. IPTables a trop de flexibilité pour que Docker puisse espérer modéliser tous les scénarios directement dans les paramètres.

Ce ticket existe apparemment depuis toujours, le comportement actuel rend Docker limite inutilisable (semble être le moyen standard d'implémenter des fonctionnalités dans ce projet, par exemple le manque de GC intégré approprié ou à peine un stockage performant, sans bug du noyau et utilisable backend,...) et il existe une solution simple pour permettre aux utilisateurs de mettre en œuvre leurs propres solutions adaptées à leur environnement.

@StefanPanait Bonne question. Mon pari est que vous auriez besoin de tirer parti de l'utilisation de groupes d'objets. Remplissez les groupes d'objets avec des adresses IP sur liste blanche, puis utilisez ce groupe d'objets dans la première ligne de la chaîne DOCKER.

Exemple:
iptables -N docker-allow
iptables -A docker-allow -s 1.1.1.1 -j ACCEPTER
iptables -A docker-allow -s 2.2.2.2 -j ACCEPTER
iptables -A docker-allow -s 3.3.3.3 -j ACCEPTER
iptables -A docker-allow -j DROP

iptables -I DOCKER -i ext_if -j docker-allow

Les règles ajoutées à la chaîne DOCKER ne sont-elles pas jamais écrasées par des choses comme le redémarrage du démon ? Je pense que le problème avec les solutions manuelles est qu'elles sont difficiles à faire correctement et il y a donc un argument solide pour mieux prendre en charge les cas courants (bloquer/autoriser les adresses IP individuelles) plus directement.

Peut-être qu'un plugin serait l'endroit approprié pour supporter cela ? Par exemple, peut-être qu'un plugin "ufw" pourrait ajouter des règles d'une manière compatible avec ufw afin que l'utilisateur puisse gérer efficacement le pare-feu avec sa chaîne d'outils de pare-feu habituelle et que les services docker se comporteraient davantage comme des services hôtes normaux.

Les règles ajoutées à la chaîne DOCKER ne sont-elles pas jamais écrasées par des choses comme le redémarrage du démon ? Je pense que le problème avec les solutions manuelles est qu'elles sont difficiles à faire correctement et qu'il existe donc un argument solide pour mieux prendre en charge les cas courants (bloquer/autoriser les adresses IP individuelles) plus directement.

L'ajout de la règle de suppression à la chaîne DOCKER-USER semble mieux fonctionner pour la rendre persistante lors des redémarrages de docker.

Avec Docker v.17.06, il existe une nouvelle chaîne iptables appelée DOCKER-USER. Celui-ci est pour vos règles personnalisées, voir ma réponse sur serverfault : https://serverfault.com/questions/704643/steps-for-limiting-outside-connections-to-docker-container-with-iptables/886257#886257

Comme je l'ai commenté sur SF, je ne comprends pas pourquoi cette chaîne DOCKER-USER est différente de toute autre chaîne ajoutée par l'utilisateur. vous devez toujours spécifier les noms d'interface vous-même et il est toujours possible que les experts non-iptables fassent de graves erreurs.

D'un autre côté, cela va toujours avec la mentalité "Docker est le seul utilisateur d'iptables" qui craint pour les personnes qui veulent utiliser iptables pour plus que juste Docker. C'est donc mauvais pour toute la gamme d'utilisateurs potentiels, à l'exception peut-être des personnes qui consacrent des hôtes entiers à rien d'autre qu'à Docker.

OK, donc l'utilisation de DOCKER-USER résout le problème de l'ordre des insertions en s'assurant qu'il arrive toujours dans la chaîne avant les autres règles liées à Docker. Cependant, cela ne facilite pas beaucoup la mise en liste blanche du trafic vers un conteneur par numéro de port, car à ce stade, le --dport est le port du service à l'

Publiez le port 9900 pour exposer un service docker écoutant en interne sur 9000.

$ sudo iptables -A DOCKER-USER -m limit --limit 20/min -j LOG --log-prefix "IPTables: "
$ docker run --rm -it -p '192.168.56.101:9900:9000' alpine nc -l 9000

Depuis une autre machine du réseau :

$ telnet 192.168.56.101 9900

Ce qui est enregistré :

IPTables: IN=enp0s8 OUT=docker0 MAC=08:00:27:b6:8d:d6:0a:00:27:00:00:04:08:00 SRC=192.168.56.1 DST=172.17.0.2 LEN=52 TOS=0x00 PREC=0x00 TTL=127 ID=14127 DF PROTO=TCP SPT=51208 DPT=9000 WINDOW=64240 RES=0x00 SYN URGP=0
IPTables: IN=docker0 OUT=enp0s8 PHYSIN=veth05ba007 MAC=02:42:0f:f9:76:4c:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=192.168.56.1 LEN=40 TOS=0x00 PREC=0x00 TTL=63 ID=23041 DF PROTO=TCP SPT=9000 DPT=51208 WINDOW=0 RES=0x00 ACK RST URGP=0

Donc, comme vous le voyez, il n'y a aucune possibilité à ce stade de filtrer le trafic vers le port 9900. Bien sûr, je pourrais filtrer le trafic vers 9000 mais c'est un problème car le port interne pourrait involontairement se chevaucher entre plusieurs conteneurs ou même des services s'exécutant sur l'hôte. C'est l'un des grands arguments de vente de Docker que vous pouvez exécuter plusieurs services sur un seul hôte sans vous soucier des conflits de ports. Ainsi, de nombreux conteneurs sont conçus pour simplement écouter sur un port et l'utilisateur peut utiliser l'option --publish pour changer quel port est exposé sur quelle interface :

$ docker run -d -p 7777:6379 --name data1 redis
$ docker run -d -p 8888:6379 --name data2 redis

Cependant, je ne peux pas utiliser DOCKER-USER (veuillez me corriger si je me trompe) pour affecter le trafic vers data1 sans affecter également le trafic vers data2 à moins que j'utilise une sorte d'introspection pour découvrir l'IP du conteneur de destination qui est transitoire qui vous ramène à la case départ pour trouver un moyen simple et fiable de pare-feu des services publiés sans script ni introspection.

Pour être clair, cela ne fonctionne pas :

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 7777 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 8888 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 7777 -j DROP
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 8888 -j DROP

Cela fonctionne, mais le résultat est que les deux services sont exposés aux deux CIDR sur liste blanche :

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 6379 -j DROP

Il semble donc que DOCKER-USER ne soit utile que pour exposer tous les ports à des adresses IP spécifiques, mais pas pour exposer des ports spécifiques à des adresses IP spécifiques, à moins que cela ne vous dérange pas d'écrire vos règles iptables pour référencer les numéros de port internes et que vous n'ayez pas plusieurs conteneurs utilisant le même numéros de ports internes. Tout le monde semble manquer ces points et fonctionner avec DOCKER-USER comme solution et je pense que cela mérite une nouvelle et meilleure solution.

@SeerUK
La solution publiée dans https://gist.github.com/SeerUK/b583cc6f048270e0ddc0105e4b36e480 n'a pas fonctionné pour moi. S'il te plait peux-tu aider?

L'erreur docker est d'utiliser iptables (pare-feu système) pour effectuer un routage d'application. Cela créera pour toujours des problèmes et le chaos. Changer iptables c'est très dangereux. Pour ajouter cette fonction intégrée dans docker, ce n'est pas si cher. De plus, il peut également avoir un statut incohérent si vous exécutez docker de manière simultanée.

Il semble que iptables ait des comportements étranges si vous modifiez iptables lorsque docker est en cours d'exécution. Si vous arrêtez le docker, définissez iptables et redémarrez le docker comme prévu. Mon souci est... si je dois changer iptables en production ?

Il semble que iptables ait des comportements étranges si vous modifiez iptables lorsque docker est en cours d'exécution.

iptables est iptables, peu importe si docker est en cours d'exécution ou non.

Il vaut mieux faire beaucoup de tests avant... dire à iptables que c'est iptables n'est qu'une tautologie. C'est ma proposition :)

Je pense que son point était que iptables ne se comporte pas différemment lorsque Docker s'exécute comme le suggère votre commentaire. Il s'agit clairement d'un problème Docker avec la façon dont ils utilisent iptables comme si personne d'autre n'en avait besoin sur le même système.

Je ne vois pas pourquoi DOCKER-USER ne résout pas cela.
Vous utilisez iptables pour filtrer comme bon vous semble : port source, port cible, adresse source, trafic non local, etc.

L'intérêt de DOCKER-USER est qu'il exécute toutes les règles que l'utilisateur souhaite exécuter avant que les règles de docker ne soient exécutées, cela vous permet de faire ce que vous voulez avec le trafic avant qu'il n'atteigne docker.

@ cpuguy83 le problème principal est que docker ouvre des ports vers le réseau public - en dehors du système - sans préavis, car il ne lie pas les ports exposés à une interface réseau (telle que eth0 ou lo) ou à une adresse IP spécifique (par exemple 127.0.0.1). 0,1, 172.16.1.1); les règles introduites par Docker n'apparaissent pas non plus dans les outils de gestion iptables comme UFW - les utilisateurs peuvent ainsi ignorer que les différents conteneurs docker sont accessibles à partir de n'importe quel système du réseau, pas seulement de leur hôte local.

DOCKER-USER ne peut pas résoudre ce problème car il ne lie pas non plus les réseaux Docker à une interface réseau spécifique ou à une adresse IP spécifique (par exemple 127.0.0.1).

Selon ma demande initiale :
1.Docker doit se lier à une adresse IP spécifique - basée sur la configuration du réseau Docker - puis permettre à l'utilisateur d'ajouter des règles pour exposer le conteneur hors système (à l'aide de l'outillage de son choix) ; par défaut, le conteneur ne doit pas être exposé hors système.

  1. Cela ne peut pas être fait sans une période de transition en raison d'au moins certaines personnes s'appuyant sur le comportement historique.

À mon humble avis, tout le problème est que Docker, comme littéralement tous les autres programmes, ne devrait pas du tout toucher le pare-feu (iptables ou autre). Lorsque j'installe (par exemple) Apache et que je lui dis d'écouter sur 0.0.0.0:80, c'est ma décision d'ouvrir ou non le port 80 sur le pare-feu, où je peux spécifier toutes les règles que je souhaite.
Au lieu de réinventer les règles de pare-feu sur les fichiers de configuration docker (et/ou composer), toute la fonctionnalité PUBLIER devrait être dépréciée et une nouvelle fonctionnalité LISTEN créée pour fonctionner comme tous les autres programmes. Au mieux, docker pourrait créer des services pare-feu désactivés par défaut pour chaque port/conteneur, sur les systèmes qui l'utilisent.

Bien dit @gcscaglia ! Encore plus déroutant, il n'y a que peu ou pas de mention de tout cela dans https://docs.docker.com/engine/reference/run/#expose -incoming-ports , je pense que c'est là que la plupart de ceux qui se soucient de chercher un peu de documentation pour compléter l'apprentissage par l'exemple aboutit à. Il devrait y avoir une case rouge vif quelque part expliquant les risques que Docker outrepasse les règles iptables préexistantes.

Si vous ne voulez pas que docker gère iptables, définissez --iptables-=false
Si vous souhaitez uniquement que docker ouvre des ports sur une interface spécifique, vous pouvez également le définir dans la configuration du démon.

@BenjamenMeyer
Vous pouvez bloquer tout le trafic dans DOCKER-USER et ne laisser passer que ce que vous voulez.

Vous pouvez bloquer tout le trafic dans DOCKER-USER et ne laisser passer que ce que vous voulez.

@ cpuguy83 Veuillez me dire si quelque chose que je dis ici est incorrect :

$ docker run -d -p 7777:6379 --name data1 redis
$ docker run -d -p 8888:6379 --name data2 redis

Cependant, je ne peux pas utiliser DOCKER-USER (veuillez me corriger si je me trompe) pour affecter le trafic vers data1 sans affecter également le trafic vers data2 à moins que j'utilise une sorte d'introspection pour découvrir l'IP du conteneur de destination qui est transitoire qui vous ramène à la case départ pour trouver un moyen simple et fiable de pare-feu des services publiés sans script ni introspection.

Pour être clair, cela ne fonctionne pas :

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 7777 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 8888 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 7777 -j DROP
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 8888 -j DROP

Cela fonctionne, mais le résultat est que les deux services sont exposés aux deux CIDR sur liste blanche :

$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 192.168.56.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp -s 10.0.24.0/24 --dport 6379 -j RETURN
$ sudo iptables -A DOCKER-USER -p tcp -m tcp --dport 6379 -j DROP

Il n'y a donc aucun moyen de contrôler le trafic vers/depuis le conteneur data1 indépendamment du conteneur data2 en utilisant DOCKER-USER . Pour moi, cela fait DOCKER-USER pas vraiment une solution.

@colinmollenhour

Si vous avez une règle de suppression générique dans DOCKER-USER , en quoi est-ce différent que si docker est ajouté au début de sa règle de saut principale ?

@ cpuguy83 mon point n'est pas contre la dérogation en soi. Ce que je dis, c'est que le fait que Docker remplace les règles iptables préexistantes devrait très certainement être une fonctionnalité d'inscription, pas une fonctionnalité de désinscription.

Et même en tant qu'opt-out - ce qui ne devrait pas être le cas - il devrait être extrêmement bien documenté car il est complètement inattendu et assez contre-intuitif à déboguer. Surtout en considérant combien utilisent ufw . Je ne connais aucun autre logiciel faisant quelque chose comme ça sans avertissements très explicites et je resterais personnellement à l'écart de ce type de logiciel.

De plus, cela est exacerbé par le fait que la plupart des utilisateurs de Docker - du moins selon mon expérience - commencent à l'utiliser dans des environnements où une infrastructure réseau supplémentaire masque le problème, renforçant l'hypothèse que la machine est configurée comme ils le pensent.

J'espère que Docker passera à une approche LISTEN only, comme illustré par

Si vous avez une règle de suppression générique dans DOCKER-USER, en quoi est-ce différent que si docker est ajouté au début de sa règle de saut principale ?

Je ne pense pas comprendre votre question... Veuillez ignorer mon commentaire du 22 avril 2017, car il ne concerne que la persistance que DOCKER-USER résout effectivement. Le problème que je signale ne concerne pas la persistance.

@taladar Qu'est-ce que vous

@jacoscaz

Premièrement, je pense que tous les mainteneurs conviennent que le comportement existant n'est pas idéal. Malheureusement, le comportement existe depuis toujours. Changer une valeur par défaut pour quelque chose qui est utilisé par des millions de personnes n'est pas vraiment quelque chose que nous pouvons faire ici. C'est l'une des raisons pour lesquelles nous avons ajouté DOCKER-USER pour qu'au moins les gens puissent injecter les règles dont ils ont besoin.

Cependant, nous ne pouvons pas contourner le besoin d'utiliser iptables (ou ebpf, ou une autre solution de nating) ET de fournir la fonctionnalité que -p fournit... revers, si vous voulez empêcher les gens de percer des trous dans le pare-feu, leur interdire d'utiliser -p .

Pour récapituler, vous pouvez :

  1. Dites à docker de se lier (par défaut) à une adresse spécifique (l'adresse par défaut est bien sûr 0.0.0.0 , ou toutes les interfaces)... mais cela n'empêchera pas quelqu'un de spécifier manuellement une adresse dans le -p spec (par exemple -p 1.2.3.4:80:80 )
  2. Injecter des règles personnalisées dans DOCKER-USER , y compris un refus de tout
  3. Désactiver la gestion d'iptables avec --iptables=false

Y a-t-il autre chose que vous pouvez suggérer qui n'inclut pas la rupture des utilisateurs existants ?

@ cpuguy83 Je comprends cela. Je ne préconise pas qu'un tel changement se produise drastiquement d'une version à l'autre. Je pense que ce serait un bon changement à prévoir au cours d'un certain nombre de versions. Je ne pense pas non plus que Docker devrait arrêter complètement d'utiliser iptables. Le transfert est différent de l' autorisation et, pour autant que je sache, Docker devrait pouvoir transférer par défaut sans autoriser l'accès à quiconque n'importe où par défaut.

En ce qui concerne les choses qui peuvent être faites maintenant, une documentation supplémentaire à ce sujet devrait être la première et la plus importante. Quel serait le canal le plus approprié pour soulever ce problème avec les responsables de docker.com ?

En l'absence d'intérêt de leur part, maintenir cette conversation vivante est probablement le meilleur moyen de maximiser les chances que ce comportement soit plus largement connu.

Je suis d'accord avec l'appel pour ajouter de la documentation supplémentaire pour cette fonctionnalité. Pour autant que je sache, il n'est mentionné que sur https://docs.docker.com/network/iptables/. Je n'ai découvert ce problème qu'après avoir essayé de comprendre pourquoi un service que j'avais limité à une adresse IP spécifique via le pare-feu était accessible au public. Ce comportement était complètement inattendu pour moi car il va à l'encontre de tous les autres services en réseau avec lesquels j'ai jamais traité.

Je comprends qu'il existe des solutions de contournement, mais je pense que cela devrait être envisagé pour un changement à long terme du fonctionnement de Docker. J'aime l'idée de le définir sur LISTEN sur un port et de permettre aux règles définies par l'utilisateur de s'appuyer sur cela.

Comme suggéré, j'ai ajouté une règle DROP à DOCKER-USER pour éviter que les conteneurs ne soient accidentellement exposés à l'extérieur (ce qui, de manière alarmante, s'est produit).

Cependant, j'ai maintenant un service que je souhaite exposer. Mais comme @colinmollenhour l' a expliqué, comme le NAT se produit avant le filtrage, je ne peux filtrer que sur l'ip docker (qui n'est pas fixe) et le numéro de port interne (qui peut être le même pour plusieurs conteneurs).

Alors, comment puis-je exposer ce service ?

@SystemParadox c'est l'une des nombreuses raisons pour lesquelles DOCKER-USER n'est pas une vraie solution au problème.

@cpuguy83
J'aime la solution LISTEN proposée et je n'ai jamais préconisé un changement radical d'une version à l'autre ; mais plutôt préconisé d'effectuer le changement sur une série de versions avec des avis appropriés, car je me rends compte que beaucoup de gens utilisent Docker et qu'un changement radical d'une version à l'autre serait préjudiciable à tous.

Je conviens également que la mise à jour de la documentation Docker concernant -p et EXPOSE, etc. devrait être une priorité immédiate pour au moins attirer l'attention sur le problème. D'après mon expérience, la plupart des utilisateurs de Docker ne sont pas des experts en pare-feu, ils font donc confiance à Docker pour faire ce qu'ils attendent, ce qui n'est pas le cas dans la conception actuelle.

De plus, les solutions de récapitulation dans https://github.com/moby/moby/issues/22054#issuecomment -425580301 ne fonctionnent pas vraiment non plus. Pourquoi? Je n'exécute pas Docker directement, j'exécute Docker Compose - basé sur le YAML ; Les adresses IP sont dynamiques (contrôlées par Docker) et je déploie souvent plusieurs services au sein du même réseau Docker qui doivent interagir les uns avec les autres. Ainsi, l'utilisation de -p et l'utilisation de la liaison d'adresse (option 1 dans le récapitulatif) ne sont pas des solutions. DOCKER-USER ne résout vraiment rien comme d'autres l'ont souligné (option 2 dans le récapitulatif) et la désactivation complète des tables IP (option 3 dans le récapitulatif) n'aide pas non plus car maintenant tout est cassé (les IP sont dynamiques donc difficile de scripter une solution ; la mise en réseau entre les conteneurs est interrompue car Docker s'appuie sur IPTables pour se déplacer entre les conteneurs ; etc.).

Encore une fois, il n'y a pas d'appel dans ce fil pour un changement de rupture entre deux versions ; mais un appel à une approche progressive planifiée qui permet aux gens de migrer de manière appropriée.

Pour contourner le problème, vous pouvez accéder au port de destination d'origine à l'aide de -m conntrack --ctorigdstport .

Donc dans mon cas j'ai les éléments suivants :

-A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow docker out
-A DOCKER-USER -s 172.17.0.0/16 -j ACCEPT
# Allow access to docker service mapped to host 8702 (the service is actually listening on port 8088 in the container)
-A DOCKER-USER -p tcp -m conntrack --ctorigdstport 8702 -j ACCEPT
# Prevent access to docker from outside
-A DOCKER-USER -j DROP

@SystemParadox

Hourra pour la bonne solution iptables ! ??

Je n'avais jamais entendu parler de --ctorigdstport mais je suppose que c'est parce que je n'ai pas lu et essayé toutes les extensions possibles d'iptables et vous êtes le premier à le mentionner dans ce contexte à ma connaissance.

J'ai testé et ça marche effectivement :

$ docker run -d -p 7777:6379 --name data1 redis
$ docker run -d -p 8888:6379 --name data2 redis
$ sudo iptables -N DOCKER-USER-redis1
$ sudo iptables -A DOCKER-USER-redis1 -s 192.168.56.0/24 -p tcp -m tcp -j RETURN
$ sudo iptables -A DOCKER-USER-redis1 -j REJECT --reject-with icmp-port-unreachable
$ sudo iptables -N DOCKER-USER-redis2
$ sudo iptables -A DOCKER-USER-redis2 -s 10.0.24.0/24 -p tcp -m tcp -j RETURN
$ sudo iptables -A DOCKER-USER-redis2 -j REJECT --reject-with icmp-port-unreachable
$ sudo iptables -A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 7777 -j DOCKER-USER-redis1
$ sudo iptables -A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8888 -j DOCKER-USER-redis2

Je pense qu'un exemple comme celui-ci appartient à la documentation car il couvre probablement ce que 99% des utilisateurs recherchent : la possibilité d'exposer les ports à l'aide de -p tout en étant capable de contrôler le trafic vers eux à l'aide de filtres courants tels que -s .

J'ai créé une demande de mise à jour de la documentation Docker concernant iptables.

https://github.com/docker/docker.github.io/issues/8087

La solution répertoriée dans https://unrouted.io/2017/08/15/docker-firewall/
semble être quelque chose de similaire, créant une chaîne iptables supplémentaire appelée FILTERS
à l'endroit où les chaînes INPUT et DOCKER-USER sautent.

@SystemParadox @colinmollenhour Après avoir testé --ctorigdstport je peux confirmer que cela fonctionne, mais avec une petite mise en garde.

Dans mon cas, j'ai une application PHP dockerisée sur Apache à l'écoute sur le port 80. Mes règles n'autorisant que 1.2.3.4 sont les suivantes :

-A DOCKER-USER -s 1.2.3.4/32 -i eth0 -p tcp -m conntrack --ctorigdstport 80 -j ACCEPT
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 80 -j DROP

Ma règle de suppression est donc un peu plus spécifique que la vôtre, ne laissant tomber que les paquets atteignant mon serveur Web – du moins c'est ce que je pensais. En fait, il laissait tomber des paquets dirigés vers mon serveur Web ainsi que des paquets retournant avec les réponses des requêtes faites par l'application PHP aux serveurs tiers.

Cela est dû au fait que --ctorigdstport ne correspond pas au port de destination sur le paquet filtré, mais sur le paquet qui a initié la connexion. Ainsi, les réponses aux requêtes sortant de Docker vers d'autres serveurs auront SPT=80 et correspondront également à --ctorigdstport 80 .

Si quelqu'un veut avoir un contrôle plus strict sur les règles DROP, --ctdir doit également être ajouté :

-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j DROP

En fait, toutes les règles permettant la connexion devraient également avoir --ctdir ajoutés pour exprimer exactement leur signification :

-A DOCKER-USER -s 1.2.3.4/32 -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j ACCEPT

@jest wow c'est vraiment important à savoir ! Je n'avais pas réalisé que cela pouvait correspondre à des paquets dans l'autre sens. Cela a du sens quand on y pense, car cela correspond à l'état de l'ensemble de la connexion, mais il est facile de le manquer lors de la lecture de la documentation.

@SystemParadox ouais, je n'ai pas eu l'occasion de

Je continue de tourner en rond avec les raisons pour lesquelles j'ai besoin de --ctdir ORIGINAL . D'une part, l'explication de @jest est parfaitement logique, et d'autre part, je n'ai normalement jamais à traiter de paquets de réponse, alors pourquoi en serait-il autrement ici ?

Je pense que la différence est que j'ai -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT comme première règle, donc le reste de mes règles ne voit jamais de paquets de réponse. Dans ce cas, je pense que --ctdir ORIGINAL n'est pas strictement requis, même s'il serait probablement plus sûr de l'inclure de toute façon.

@jest , êtes-vous d'accord avec ça ? Vraisemblablement, vous n'avez pas ESTABLISHED,RELATED -j ACCEPT règle

@jest Votre message a été d'une grande aide, merci.

Votre approche est-elle requise uniquement pour les éléments gérés par docker, ou pour tout ? Par exemple, mon port ssh (22) n'a rien à voir avec docker. Dois-je utiliser -m tcp --dport 22 comme d'habitude, ou dois-je utiliser -m conntrack --ctorigdstport 22 --ctdir ORIGINAL ?

Je suppose que votre approche n'est nécessaire que pour le trafic géré par docker, car ces paquets subissent une mutilation/natting avant de me parvenir dans la table des filtres. MAIS, je suis nouveau sur iptables donc je veux être sûr de quelqu'un qui en sait plus que moi !

@lonix1 Les règles sont ajoutées par docker à la chaîne DOCKER uniquement si vous exposez un port, et probablement uniquement lorsque le conteneur est en cours d'exécution. Si, dans aucun de vos conteneurs, vous n'exposez le port 22, vos règles de pare-feu devraient fonctionner sans modification.

@SystemParadox Cela fait un certain temps et je n'ai pas accès à ce serveur pour vérifier, mais si je me souviens bien, il y avait des règles ESTABLISHED,RELATED (gérées par UFW, dans la chaîne ufw-before-input ). Cependant, dans mon cas, ils ne correspondraient pas au premier paquet (SYN) de connexions établies entre Docker et les hôtes Internet sur le port 80. Et seraient supprimés par la règle appropriée dans DOCKER-USER lorsqu'il n'y avait pas de --ctdir .

@ Ionix1 , les paquets pour les services sur l'hôte ne passent que par INPUT, tandis que les paquets pour les services docker ne passent que par FORWARD et DOCKER-USER.

Par exemple, avec une adresse IP externe de 10.0.0.1 et deux conteneurs avec -p 4000:80 et -p 4001:80 , vous verrez des paquets avec les propriétés suivantes :

INPUT:
dst 10.0.0.1 dport 80 ctorigdst 10.0.0.1 ctorigdstport 80
FORWARD/DOCKER-USER:
dst 172.17.0.5 dport 80 ctorigdst 10.0.0.1 ctorigdstport 4000
dst 172.17.0.6 dport 80 ctorigdst 10.0.0.1 ctorigdstport 4001

Vous pouvez donc utiliser en toute sécurité --dport 80 pour les règles INPUT, car elles sont dans des chaînes totalement séparées. Comme vous pouvez le voir, --ctorigdstport 80 correspondrait toujours, mais à moins que vous ne modifiiez également les entrées pour une raison quelconque, je ne le ferais probablement pas.

Vous remarquerez peut-être également que vous pouvez utiliser --dport 80 avec --dst 172.17.0.5 pour filtrer les paquets pour un conteneur Docker spécifique, mais cette IP n'est pas prévisible, c'est pourquoi nous utilisons --ctorigdstport .

En fin de compte, vous devez savoir à quels paquets une règle donnée peut correspondre, en fonction de la chaîne dans laquelle vous vous trouvez, de la destination et de l'éventuelle altération.

@jest merci, cela semblerait confirmer ma pensée.

Du coup, j'ai besoin d'un petit conseil... pour passer de UFW à iptables...
-est-ce que je désactive simplement UFW via "ufw disable" ?
-est-ce que je crée mon propre fichier .sh, ou en existe-t-il un (je suis Ubuntu sur DigitalOcean) ?
-J'ai juste besoin de dire à Docker "--iptables=false" ? (c'est tout pour Docker ?)
-DOCKER-USER est déjà créé et chaîné par Docker ?

@fredjohnston, vous pouvez continuer à utiliser UFW si vous le souhaitez. Les profils sont stockés dans /etc/ufw . Le problème ici est que Docker ne s'affichera pas car aucun profil d'application n'est répertorié dans /etc/ufw/applications.d (identique pour tout autre outil de pare-feu et sa configuration).

La désactivation d'IPTables dans Docker signifie que vous n'aurez pas beaucoup de réseau dans Docker, seules les adresses IP et les conteneurs ne pourront pas communiquer entre eux. DOCKER_USER est un hack pour vous donner un certain contrôle, mais ne résout vraiment pas le problème - qui consiste vraiment à ne pas rendre les conteneurs Docker publics sur le réseau par défaut, mais verrouillés sur l'adresse IP du conteneur.

Pour le moment, je vous recommande de continuer à utiliser l'outil de pare-feu avec lequel vous êtes le plus à l'aise (ufw, etc.), mais sachez que les conteneurs Docker seront publics sur votre réseau.

Étant donné que je suis ici de quelque manière que ce soit, je rencontre actuellement un problème connexe où il s'agit également d'un problème sur des plates-formes autres que Linux. Considérer ce qui suit:

  • Le projet A comporte deux conteneurs, un pour une base de données et un pour une application.
  • Le projet B a deux conteneurs, un pour une base de données et un pour une application.
  • Les deux projets sont isolés l'un de l'autre (référentiels sources séparés, configurations, etc.)
  • Les deux projets sont gérés sous Docker Compose
  • Les deux projets exposent leurs ports de base de données à des fins de développement local
  • Les deux projets utilisent le même serveur de base de données (postgres, mysql, etc.)

Supposons maintenant que vous souhaitiez exécuter les deux projets localement - par exemple pour travailler sur une bibliothèque partagée que les deux projets utilisent afin que vous puissiez facilement l'intégrer dans leur code pour les tests.

Dans la conception actuelle de l'interaction du pare-feu - et ce qui conduit en partie aux problèmes ci-dessus concernant l'exposition du conteneur au réseau public à l'insu de l'utilisateur - les dockers de base de données des deux projets ne peuvent pas être exécutés en même temps car ils seront en conflit pour le port exposé pour la base de données. Idem si vous vouliez exposer à la fois leur port d'application et qu'ils utilisaient tous les deux le même port pour le serveur d'applications - courant puisque les API et les applications basées sur HTTP sont extrêmement courantes maintenant, en particulier dans les applications orientées cloud.

Bien sûr, vous pouvez configurer les deux sous un même conteneur DB ; mais vous ne les isolez pas selon la conception de votre projet et devez faire encore plus attention aux configurations, etc.

Dans une solution appropriée, voici deux volets :

  1. Les conteneurs ne seraient liés qu'à leurs adresses IP et leurs ports exposés seraient liés à leurs adresses IP uniquement au sein de leurs réseaux Docker respectifs, et non sur le système, toutes les adresses IP ( 0.0.0.0 , :: ).
  2. Docker n'exposerait pas non plus publiquement la route aux réseaux Docker hors du système par défaut. Docker Networking peut être utilisé pour établir des connexions inter-réseaux (réseau docker à réseau docker) telles qu'elles sont actuellement conçues, puis autoriser les connexions hôtes locales par défaut.
  3. Les utilisateurs seraient alors obligés d'ajouter les règles de pare-feu appropriées pour exposer le conteneur au monde extérieur quand et s'ils le souhaitent - par exemple, en redirigeant le port 443 vers le port 443 de leur conteneur de choix.

Encore une fois, cela pourrait se faire progressivement :

  • Version 1 : Mettre en œuvre les étapes 1 et 2 ; mais ajoutez également le routage non localhost (temporairement) avec un avertissement. Le comportement actuel du premier arrivé, premier servi pour obtenir un port est maintenu ; et un avertissement concernant la disparition de ce comportement est émis.
  • Version 1+N : supprimez l'avertissement et supprimez le routage non localhost. Exigez l'étape 3 pour les utilisateurs qui souhaitent que Docker expose les ports hors système et assurez-vous que cela est bien documenté.

DOCKER_USER est un hack pour vous donner un certain contrôle, mais ne résout vraiment pas le problème - qui consiste vraiment à ne pas rendre les conteneurs Docker publics sur le réseau par défaut, mais verrouillés sur l'adresse IP du conteneur.

Pour le moment, je vous recommande de continuer à utiliser l'outil de pare-feu avec lequel vous êtes le plus à l'aise (ufw, etc.), mais sachez que les conteneurs Docker seront publics sur votre réseau.

Il y a une demande de mise à jour de document ici https://github.com/docker/docker.github.io/pull/8357 pour créer une configuration statique iptables boulonnée mais je ne suis pas sûr de son statut.

Edit : J'ai remarqué que vous avez mal compris le sens de DOCKER-USER. Il n'est pas utilisé pour "verrouiller les conteneurs sur l'adresse IP du conteneur" mais pour filtrer l'accès au conteneur avec des règles iptables.

@aki-k par DOCKER_USER Je sais que DOCKER_USER ne verrouille pas le conteneur contre l'adresse IP et n'a jamais prétendu qu'il l'avait fait. DOCKER_USER confie simplement la gestion du problème de sécurité à l'utilisateur - ce qui signifie que l'utilisateur doit savoir comment manipuler son pare-feu pour s'assurer qu'il dispose réellement d'un environnement sécurisé. En faire un problème d'utilisateur est tout aussi inacceptable car la plupart des utilisateurs ne savent pas comment gérer leur pare-feu - les règles de pare-feu sont difficiles, et même ceux d'entre nous qui connaissent bien l'écriture de règles de pare-feu peuvent encore souvent se tromper.

Ma demande - et tout le problème - dans le problème est que Docker doit être sécurisé par défaut et ne pas exposer les conteneurs au monde extérieur à l'insu de l'utilisateur ou à l'intervention explicite pour le faire. Et selon mon dernier commentaire (https://github.com/moby/moby/issues/22054#issuecomment-552951146), cela aura également d'autres avantages techniques.

Ma demande - et tout le problème - dans le problème est que Docker doit être sécurisé par défaut et ne pas exposer les conteneurs au monde extérieur à l'insu de l'utilisateur ou à l'intervention explicite pour le faire.

Le premier rapport de problème que j'ai trouvé à ce sujet date du 18 mars 2014 :

https://github.com/moby/moby/issues/4737

C'est probablement une décision de conception qu'ils ne veulent pas changer et qu'ils essaient de corriger avec la chaîne iptables DOCKER-USER. Vous pouvez utiliser l'option -p pour docker run pour publier le port uniquement sur l'hôte docker (-p 127.0.0.1:port:port) mais cela ne résout pas le problème non plus.

Soyons clairs, Docker est sécurisé par défaut. Vous devez dire à Docker de faire la redirection de port.

En ce qui concerne les services qui doivent communiquer entre eux, vous devez utiliser des réseaux ( docker network ) pour limiter l'accès, pas la redirection de port.

Soyons clairs, Docker _Est_ sécurisé par défaut. Vous devez dire à Docker de faire la redirection de port.

Veuillez ne pas ignorer un problème factuel avec docker fonctionnant sur votre protection iptables établie. Je suis sûr que vous avez vu les innombrables rapports sur les problèmes qui en traitent.

Soyons clairs, Docker _Est_ sécurisé par défaut. Vous devez dire à Docker de faire la redirection de port.

En ce qui concerne les services qui doivent communiquer entre eux, vous devez utiliser des réseaux ( docker network ) pour limiter l'accès, pas la redirection de port.

Soyons clairs - Docker est sécurisé jusqu'à ce que vous souhaitiez y accéder en dehors du réseau Docker. Une fois que vous souhaitez y accéder même à partir de l'hôte local (127.0.0.1), Docker n'est pas sécurisé par défaut car il se lie à 0.0.0.0 et expose les conteneurs hors du système - en contournant tout ce que l'utilisateur fait pour sécuriser son système, sans se présenter dans l'outillage autre que l'utilisation directe de iptables . DOCKER_USER n'est pas et ne sera jamais une solution appropriée car il nécessite que l'utilisateur en sache trop sur le système de pare-feu sous-jacent (iptables sous Linux, quel que soit le Mac utilisé, et le pare-feu Windows sous Windows). Cela doit toujours être sécurisé par défaut. Il existe un moyen très simple de le faire, comme je l'ai décrit plus tôt dans https://github.com/moby/moby/issues/22054#issuecomment -552951146 et j'ai appelé à plusieurs reprises tout au long de ce numéro (bien que peut-être pas aussi clairement que dans cela commenter).

Notez également que la documentation pour exposer un port ne mentionne pas non plus ce problème de sécurité, ne faisant aucune référence à la façon dont Docker interagit avec le pare-feu du système lors de l'exposition d'un port - réduisant ainsi la sécurité sur les systèmes qui avaient été conçus pour être sécurisés jusqu'à ce que Docker soit utilisé .

Soyons clairs, Docker _Est_ sécurisé par défaut. Vous devez dire à Docker de faire la redirection de port.
En ce qui concerne les services qui doivent communiquer entre eux, vous devez utiliser des réseaux ( docker network ) pour limiter l'accès, pas la redirection de port.

Soyons clairs - Docker est sécurisé jusqu'à ce que vous souhaitiez y accéder en dehors du réseau Docker. Une fois que vous voulez y accéder même à partir de l'hôte local (127.0.0.1), Docker n'est _pas_ sécurisé par défaut car il se lie à 0.0.0.0 [...]

Pour être précis, Docker ne se lie pas à 0.0.0.0. C'est l'attente (raisonnable) de l'utilisateur : que la spécification de --publish sur la CLI, fonctionnant donc dans l'espace utilisateur, démarre une sorte de démon proxy qui écoute sur le port spécifié, accepte les connexions entrantes et effectue des va-et-vient tous les paquets entre Docker et le monde extérieur.

Mais à la place, Docker injecte des règles magiques DNAT/masquarading dans le pare-feu pour réécrire les adresses sur le paquet, brisant ainsi au hasard tout système de règles préinstallé.

À mon avis, déranger les niveaux d'abstraction ici est la plus grande déception et confond l'utilisateur. Je ne sais pas quels scénarios ont été envisagés par l'équipe Docker lors de la conception de la machinerie --publish nous voyons ici, je n'en vois aucun qui justifie la décision (peut-être à côté des raisons de performance).

Soyons clairs, Docker est sécurisé par défaut. Vous devez dire à Docker de faire la redirection de port.

... qui se trouve être l'une des fonctionnalités les plus utilisées de Docker. En effet, vous venez de déclarer qu'un remplacement non documenté des règles de pare - feu

Eh bien, tout ce qui fonctionne pour vous, je suppose. Comme cela ne fonctionne pas pour moi, je vais commencer à chercher des plates-formes de conteneurs alternatives. Les problèmes de sécurité peuvent être résolus, mais le mépris flagrant des attentes de sécurité raisonnables est une bête différente.

Évaluons la situation telle qu'elle est aujourd'hui :

Par défaut, aucun port n'est exposé. Vous devez dire à Docker d'exposer un port.
Dans le passé, Docker configurait iptables de telle sorte que tout ce qui savait comment se diriger vers le réseau de pont puisse accéder aux adresses IP du conteneur (en définissant la politique de transfert sur "accepter"), mais ce n'est plus vrai.

J'ai vu des personnes dire qu'elles utilisaient -p pour exposer des services les unes aux autres, ce qui ne devrait pas être obligatoire.
Sur le réseau par défaut, vous pouvez utiliser --link pour connecter des services ensemble, le conteneur est disponible via DNS.
Sur les réseaux autres que ceux par défaut (réseaux définis par l'utilisateur), les conteneurs peuvent également s'accéder par DNS, y compris la configuration d'alias via --link , ou même vers un réseau avec un alias spécifié.

Il semble que dans de nombreux cas, vous vouliez simplement vous connecter au service à partir du client, auquel cas il est recommandé d'utiliser un autre conteneur avec accès au service auquel vous souhaitez vous connecter plutôt que d'exposer un port.

-p est spécialement conçu pour l'entrée, comme pour permettre aux éléments externes d'accéder à ce service.
La valeur par défaut pour -p est en effet d'autoriser le trafic de n'importe où. Vous pouvez modifier cela en spécifiant manuellement l'adresse à autoriser soit par -p soit en tant que paramètre à l'échelle du démon.
Étant donné que -p utilise iptables, la chaîne DOCKER-USER été créée afin que les utilisateurs puissent ajouter leurs propres règles de filtrage avant qu'elles n'atteignent le conteneur.

Étant donné que -p est conçu pour l'entrée, je pense qu'il est raisonnable que cela expose le trafic comme il le fait. J'ai l'impression qu'il est regrettable que les règles Docker soient insérées en haut de la table de filtrage, mais changer cela serait vraiment un changement décisif pour un grand groupe d'utilisateurs qui veulent ce comportement.

Il existe quelques autres alternatives à -p :

  1. N'utilisez pas -p , connectez-vous directement à l'IP du conteneur. Cela nécessite un peu de travail supplémentaire puisque vous devez rechercher l'IP, mais ces données sont disponibles à partir de l'API. Vous devrez également vous assurer que la politique de transfert sur le pare-feu le permet (en supposant que vous vous connectez à partir d'un hôte différent, le même hôte devrait convenir)
  2. Utilisez la mise en réseau macvlan ou ipvlan pour les services que vous souhaitez rendre accessibles depuis le réseau de l'hôte (c'est-à-dire l'entrée). Ces options de mise en réseau donnent au conteneur une adresse IP directement à partir de l'interface réseau de l'hôte (vous choisissez à quelle interface il est lié).
  3. Utilisez --net=host , cela exécute le service sur l'espace de noms du réseau hôte donnant au service l'accès à l'infra réseau qui existe déjà sur l'hôte.

Vous dites « rendre cela sécurisé par défaut », mais exposer un port est par définition une action potentiellement non sécurisée. Il semble également y avoir une idée que l'exposition à localhost uniquement est sécurisée, mais ce n'est pas le cas car tout ce qui s'exécute sur l'hôte peut accéder à localhost (y compris javascript dans un navigateur s'il s'agit d'un ordinateur de bureau).

Quel cas d'utilisation essayez-vous de résoudre en utilisant -p ?
Avez-vous des idées sur le changement réel que vous aimeriez voir?

Je vais bien changer quelque chose pour améliorer cela pour votre flux de travail, mais il y a beaucoup de cas d'utilisation différents ici et une taille ne convient jamais à tous (voir les plaintes concernant -p ).

@ cpuguy83 tout va bien et dandy, mais ne change rien à cette demande.

Les gens utilisent docker et souhaitent se connecter à des applications exécutées sous docker à partir de leur système local - il s'agit d'un cas d'utilisation extrêmement courant pour les développeurs, en particulier lorsqu'ils essaient de diagnostiquer quelque chose ou écrivent un service qui n'est pas sous docker mais doit se connecter à des services hébergés dans docker (pour remplir un environnement de développement). Développer sous Docker n'est pas toujours une expérience bonne, amusante ou utile non plus, en disant :

il est recommandé d'utiliser un autre conteneur avec accès au service auquel vous souhaitez vous connecter plutôt que d'exposer un port.

Est tout simplement un non-aller. Tous les outils ne sont pas compatibles avec Docker, et ils ne devraient pas non plus l'être. L'utilisateur ne devrait pas être obligé d'être entièrement sous Docker pour utiliser les services exécutés sous Docker. Même dans ce cas, si Docker est utilisé pour faire fonctionner des serveurs, l'administrateur ne peut pas facilement les contrôler via la configuration du pare-feu et la fonctionnalité Expose Port, mais elle est orchestrée (ligne de commande, Dockerfile, Docker Compose Config) est complètement cassée.

De plus, les gens utilisent Docker Compose pour gérer une grande partie de l'environnement et spécifient via docker-compose.yml ou Dockerfile qu'un port doit être exposé afin qu'ils puissent y accéder localement. Par conséquent, dire le paramètre use the -p est incorrect car ils ne s'interfacent jamais directement avec la commande docker de manière à ce que cela fonctionne.

Exposer un port ne signifie pas que la sécurité doit être rompue. J'ai expliqué comment vous pouvez exposer un port au système local sans enfreindre la sécurité (https://github.com/moby/moby/issues/22054#issuecomment-552951146) et cela mettrait la gestion de l'exposition externe hors tension système) entre les mains de l'utilisateur d'une manière qu'il peut facilement contrôler avec son outillage existant.

Solution:

  • Utiliser le réseau Docker
  • Exposer les ports sur le réseau Docker seul et l'hôte local
  • Arrêtez de lier le port sur 0.0.0.0 - ou de le faire efficacement
  • Exiger que les utilisateurs utilisent leur propre outil de pare-feu pour exposer le port hors système (ufw, firewalld, etc.)
  • Fournir des intégrations pour les pare-feu courants pour rendre cela facile

En d'autres termes, au lieu d'accéder à un service dans un conteneur docker via 127.0.0.1:<port> devez demander <docker container ip>:<service port> même à partir de l'hôte local. Si les utilisateurs souhaitent exposer le service hors système, ils peuvent ajouter une règle de pare-feu via leurs outils (ufw, etc.) pour transférer le port d'un port donné vers <docker container ip>:<service port> .

Vous pouvez également suivre l'approche Kubernetes avec leur conception de proxy, en faisant comme @jest suggéré dans https://github.com/moby/moby/issues/22054#issuecomment -554665865. Encore une fois, cela devrait toujours être un système local jusqu'à ce que l'utilisateur l'ait délibérément exposé au réseau extérieur.

Le plus gros problème ici est que Docker compromet l'intégrité du pare-feu afin de fournir ses services, sans rien dire à l'utilisateur et sans s'intégrer dans les outils de l'utilisateur afin que l'utilisateur ne le sache ni ne puisse le contrôler.

Je me rends compte qu'il existe une tonne d'interfaces de pare-feu sur le marché - même avec iptables, il y a firewalld, ufw et une douzaine d'autres. Je ne m'attends pas vraiment à ce que Docker s'intègre avec eux (même si ce serait bien), mais je m'attends à ce que Docker fonctionne de manière à ne pas les contourner ou à briser la sécurité que l'utilisateur a configurée.

Comme cas de test de base :

  • Configurer un serveur basé sur Debian (Debian, Ubuntu)
  • Installer ufw, OpenSSH Server
  • courir ufw allow OpenSSH
  • courir ufw enable

À ce stade, vous disposez d'un serveur assez sécurisé ; le seul trafic autorisé est soit (a) lié au trafic sortant, soit (b) au trafic du serveur SSH.

Maintenant, démarrez un conteneur Docker avec un port exposé.

Une solution acceptable répondrait aux critères suivants :

  • Le port ne doit pas être accessible depuis un autre ordinateur ; le pare-feu doit continuer à le bloquer des systèmes extérieurs.
  • Le port doit être accessible depuis l'ordinateur local (localhost).
  • Plusieurs conteneurs docker devraient pouvoir exposer le même port et tout faire fonctionner ; tous les conteneurs avec un port exposé doivent être accessibles à partir du système local uniquement.
  • L'utilisateur ne devrait pas avoir à connaître les détails de la configuration de son pare-feu pour que cela se produise.

Essayez la même chose pour un système basé sur CentOS/Fedora/RHEL avec firewalld .

@cpuguy83 Les utilisateurs de l' OMI s'attendent -p ce que -p 80:80 , je m'attends au comportement comme si l'application liée au port 80 dans le conteneur s'exécutait sur l'hôte.

Les ports de modèle VirtualBox ou SSH sont transférés de cette manière, de sorte que les gens supposent que c'est la même chose dans le cas de Docker.

Pour utiliser un parallèle d'attente plus large : du point de vue de l'utilisateur, il devrait fonctionner comme des volumes liés à l'hôte. Vous pointez simplement sur le répertoire hôte, et "comme par magie", il est visible à l'intérieur du conteneur ; toutes les modifications apportées au système de fichiers de l'autre côté fonctionnent de la même manière, y compris les autorisations, les quotas, etc.

Réduire les problèmes de -p aux cas de pare-feu : dans le monde normal, l'utilisateur s'attend à ce qu'une application liée à 0.0.0.0:80 soit visible du monde extérieur. Si l'utilisateur souhaite limiter l'accès, il est informé dans de nombreux guides d'utilisation d'un pare-feu et de configuration d'une règle dans la chaîne INPUT :

-P INPUT DROP
-A INPUT -s 1.2.3.4/32 -p tcp --dst-port 80 -j ACCEPT

ou utilisez des outils comme UFW :

ufw enable
ufw allow http

Mais avec docker, l'utilisateur doit être capable de construire de telles règles à l'improviste :

-A DOCKER-USER -s 1.2.3.4/32 -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j ACCEPT
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j DROP

S'il vous plaît, montrez-moi un guide complet pour un utilisateur moyen sur la façon de résoudre ces scénarios courants avec Docker.

Et pour, par exemple, un développeur moyen - à qui vous avez vendu tout le concept de "conteneurisation d'applications" - les conséquences de l'exposition d'un portage sont tout simplement imprévisibles.

Ces jours-ci, je n'expose tout simplement pas les ports en raison de mon manque de connaissances et j'utilise plutôt des services comme Traefik. Peut-être que --expose ne devrait pas non plus être proposé comme un usage général dans les documents CLI, car IMO, cela ne fait tout simplement aucun bien pour un utilisateur moyen.

Ou bien, fournissez à quiconque n'ayant pas une connaissance approfondie de ces outils un ordinateur portable Linux, tel qu'un Dell ou un Lenovo, et constatez à quel point leurs efforts sincères pour configurer correctement leurs pare-feu ne font absolument aucune différence lorsque quelqu'un parvient à accéder à leur base de données locale tout en prendre un café chez Starbucks.

Ce problème précis a causé pour nous une vulnérabilité du système que j'ai découverte au cours du week-end. Par @jacoscaz , j'ai juste pu puiser dans la base de données locale d'un autre développeur car elle a un port publié. Ce serait formidable si vous pouviez tous inclure cela dans la documentation d'introduction afin que les autres ne fassent pas de même. Nous avons besoin d'un service non conteneurisé pour se connecter à un conteneur sans que tout le monde sur le réseau y ait accès, donc les réseaux Docker sont hors de question. Il semble que la meilleure option pour le moment soit de se connecter à l'adresse IP du conteneur local, à moins que quelqu'un n'ait une meilleure idée.

@dentonmwood Cela ressemble exactement à ce que vous devriez faire. Comme je l'ai mentionné ci-dessus, vous pouvez même configurer le service pour qu'il s'exécute en utilisant macvlan ou ipvlan, ce qui lui donnera une adresse IP directement sur votre réseau normal.

@BenjamenMeyer @jest @jacoscaz

Merci pour les commentaires supplémentaires.

Je suis d'accord que les connaissances que nous demandons aux utilisateurs de poser à cet égard ne sont pas excellentes. Il incombe actuellement à l'utilisateur (que ce soit un administrateur système ou un développeur) de comprendre ce que fait -p et de prendre les précautions appropriées pour garantir que le service n'est exposé qu'à qui il pense qu'il est exposé. Nous allons encore plus loin même en attendant que les développeurs qui n'ont souvent aucune raison de connaître iptables interviennent et le corrigent pour leur propre environnement (via DOCKER-USER ).
Nous sommes fondamentalement dans cette situation en raison des craintes de rupture de compatibilité.

J'ai quelques idées auxquelles je n'ai pas encore eu le temps de bien réfléchir, mais cela changerait essentiellement le comportement de -p fonction de la version API du client et gérerait l'entrée séparément. Encore un changement décisif qui m'inquiète mais au moins l'ancien comportement est conservé dans les anciennes versions de l'API.

J'ai pensé que nous pourrions faire un proxy local (ala kubectl proxy ) pour le cas d'utilisation de l'accès local, mais cela impose encore une fois au développeur d'en savoir et de comprendre plus que ce dont il aurait vraiment besoin .

Les pensées?

Je pense que l'introduction du proxy est un pas dans la bonne direction.

Je reconnais que le "port forwarding" est une responsabilité partagée entre un conteneur et un orchestrateur, similaire dans l'esprit au mode Swarm. Mais l'intégration minimale de l'hôte est toujours requise et lorsqu'on donne une option comme -p à l'utilisateur (non expert), la solution proposée doit être compréhensible pour une telle personne.

Comme les conséquences -p sur l'environnement ne sont pas spécifiées (au moins au niveau de la CLI, je pense), il est possible d'introduire une configuration conviviale de son fonctionnement.

Par exemple, pour le moment, il y a iptables dans /etc/docker/daemon.json qui détermine s'il faut ou non manipuler DOCKER . La configuration pourrait être étendue avec une autre entrée, de telle sorte qu'une combinaison comme iptables==true && expose_with_network_proxy==true activerait le comportement de type proxy.

Cela devrait être généralement sans danger pour les nouvelles installations, comme on préférait overlay2 à aufs quelque temps (si je me souviens bien). Et facilement déployé en tant que tel, car les mises à niveau ne touchent pas les fichiers de configuration existants, mais les nouvelles installations sont gratuites.

Nous sommes fondamentalement dans cette situation en raison des craintes de rupture de compatibilité.

J'aimerais vraiment souligner le fait que la gravité de ce problème, du moins à mes yeux, a plus à voir avec le manque de documentation qu'avec les fondements techniques actuels de -p . Oui, je ne crois pas que le mécanisme actuel puisse être qualifié de _sécurisé_, mais les failles de sécurité peuvent être corrigées et je ne suis pas en position de critiquer les raisons qui ont conduit au scénario actuel. Le recul est toujours 20/20, après tout, et j'admire l'engagement envers la compatibilité descendante et la volonté de relever les défis techniques qu'un tel engagement apporte.

Ce que je ne comprends pas, c'est pourquoi il n'y a toujours pas de grandes cases rouges lumineuses dans la documentation de Docker mettant explicitement en garde contre les effets secondaires de l'utilisation de -p . Si j'avais rencontré un tel avertissement, je ne pense pas que je serais même ici en premier lieu.

Les pensées?

L'option proxy semble raisonnable. De plus, j'aime l'idée de pouvoir exposer et désexposer les ports chaque fois que nécessaire plutôt que d'être obligé de le faire lors du lancement de docker run .

@BenjamenMeyer @jest @jacoscaz

Merci pour les commentaires supplémentaires.

Merci d'avoir enfin regardé ça de plus près.

Je suis d'accord que les connaissances que nous demandons aux utilisateurs de poser à cet égard ne sont pas excellentes. Il incombe actuellement à l'utilisateur (que ce soit un administrateur système ou un développeur) de comprendre ce que fait -p et de prendre les précautions appropriées pour garantir que le service n'est exposé qu'à qui il pense qu'il est exposé. Nous allons encore plus loin même en attendant que les développeurs qui n'ont souvent aucune raison de connaître iptables interviennent et le corrigent pour leur propre environnement (via DOCKER-USER ).
Nous sommes fondamentalement dans cette situation en raison des craintes de rupture de compatibilité.

J'ai quelques idées auxquelles je n'ai pas encore eu le temps de bien réfléchir, mais cela changerait essentiellement le comportement de -p fonction de la version API du client et gérerait l'entrée séparément. Encore un changement décisif qui m'inquiète mais au moins l'ancien comportement est conservé dans les anciennes versions de l'API.

Les pensées?

  1. Commencez par mettre à jour la documentation afin que les utilisateurs soient conscients de l'effet d'EXPOSER de toutes les méthodes (-p, Dockerfile, docker-compose.yml). Cela peut être fait rapidement. Cela peut également aider à attirer l'attention sur le problème pour obtenir plus de personnes offrant des conseils/expertises pour trouver une bonne solution et aider à répondre s'il existe un désir de la communauté pour une compatibilité descendante également.
  2. Planifiez une méthode pour aller de l'endroit où Docker se trouve maintenant à l'endroit où Docker doit être. Cela peut inclure un changement décisif à un moment donné.

Je pense que la gravité de la situation permet certainement un changement décisif pour y remédier ; ne le faites pas sans en informer les gens. Fixez un échéancier (6 mois ? 12 mois ?) où le changement est fortement discuté, etc. et vous préparez la communauté ; puis dans une version "majeure" faire le changement. Sur la base de votre schéma de version, il semble que le premier ensemble soit l'année (19); étant donné que nous sommes à la fin de 2019, utilisez le reste de 2019, puis 2020 pour élaborer la solution et la publier ; présentez-le en tant que fonctionnalité facultative dans le courant de 2020, puis faites-en la promotion en premier lieu/utilisation par défaut en 2021.

La version Dockerfile et la version Docker Compose Schema peuvent également être de bons outils pour définir un comportement par défaut, mais je n'empêcherais pas les anciennes versions de pouvoir en profiter et je mettrais un avertissement fort sur la nécessité de mettre à jour dans un tel cas aussi.

Quelle que soit la manière dont il est déployé, je pense que vous trouverez la communauté dans son ensemble beaucoup plus favorable à un tel changement si elle comprend pourquoi et ce qui se passe et n'a pas l'impression d'être aveuglée par cela.

J'ai pensé que nous pourrions faire un proxy local (ala kubectl proxy ) pour le cas d'utilisation de l'accès local, mais cela impose encore une fois au développeur d'en savoir et de comprendre plus que ce dont il aurait vraiment besoin .

J'aime l'idée du proxy, et la suggestion de @jest sur la façon de l'activer peut très bien être une excellente méthode pour effectuer la migration également.

Honnêtement, la plupart des gens qui chercheront à l'exposer hors du système seront ou devraient être familiers avec les pare-feu dans une certaine mesure, même s'il s'agit simplement de configurer UFW ou firewalld pour le faire. Donc, si la solution ne la rend disponible qu'à l'hôte local, alors je pense qu'il est acceptable que les gens apprennent à utiliser leurs outils de pare-feu pour effectuer une redirection de port à travers le pare-feu.

Je pense qu'il est important que cette fonctionnalité soit effectuée via l'outil de pare-feu que l'utilisateur a décidé d'utiliser, car cela lui rendra tout visible. Une telle fonctionnalité ne doit pas contourner leur outillage. Je me rends également compte qu'il existe tellement d'outils de pare-feu qu'il est déraisonnable pour Docker de s'intégrer à tous. Je suggérerais donc de suivre un itinéraire de documentation et de souligner comment le faire avec certains des plus populaires pour commencer, et de laisser la communauté en ajouter et les mettre à jour.

J'aime l'idée du proxy, et la suggestion de @jest sur la façon de l'activer peut très bien être une excellente méthode pour effectuer la migration également.

Honnêtement, la plupart des gens qui chercheront à l'exposer hors du système seront ou devraient être familiers avec les pare-feu dans une certaine mesure, même s'il s'agit simplement de configurer UFW ou firewalld pour le faire. Donc, si la solution ne la rend disponible qu'à l'hôte local, alors je pense qu'il est acceptable que les gens apprennent à utiliser leurs outils de pare-feu pour effectuer une redirection de port à travers le pare-feu.

Je fais écho à ça. Avec la solution proxy, je pense qu'il serait possible de proposer quelque chose qui permettrait aux utilisateurs de lier le proxy à la combinaison d'interfaces et de ports qu'ils souhaitent. Cependant, si vous êtes obligé de faire des compromis au nom de la rétrocompatibilité (que je soutiens !)

Suite à mon commentaire, le proxy pourrait en fait fonctionner de différentes manières en fonction des outils de pare-feu disponibles. Si un outil de configuration de pare-feu pris en charge était détecté, le proxy pourrait l'utiliser pour définir les règles de transfert appropriées. La présence d'un tel outil pourrait être ajoutée comme une exigence pour les environnements de production. Si un tel outil n'était pas disponible, le proxy générerait par défaut un serveur proxy au niveau de l'application.

Docker contourne le pare-feu macOS de la même manière que sous Linux.

Sur ma machine de développement (Docker Desktop pour Mac), j'ai ajouté "ip": "127.0.0.1" à la configuration du démon Docker. De cette façon, toutes les bases de données de développement, etc., ne seront par défaut accessibles qu'à partir de localhost. Pour les rares cas où j'ai besoin de rendre une application visible aux autres, je peux la publier explicitement avec -p 0.0.0.0:8080:8080 .

Modifier : il semble que Docker Compose se lie toujours à 0.0.0.0 par défaut, quelle que soit la configuration du démon Docker. Cette astuce ne fonctionne donc que lors de l'exécution de Docker à partir de la ligne de commande. Et de toute façon, étant donné que les fichiers Compose sont souvent partagés avec des coéquipiers qui peuvent avoir une configuration système différente, il vaut mieux ajouter 127.0.0.1 explicitement au fichier Compose.

@luontola Quelle solution élégante.

Edit: Ce que je pensais aussi était peut-être un moyen d'avoir une liste d'adresses autorisées/bloquées qui pourraient être définies dans Docker, par exemple, qui seraient ensuite automatiquement ajoutées à la chaîne DOCKER-USER iptables.

Une petite marguerite oopsie qui souligne l'importance de ce problème : https://github.com/docker/for-linux/issues/810 (résumé : DOCKER-USER a été supprimé donc même si vous avez configuré iptables pour bloquer l'accès externe aux dockers, il ignorerait cette configuration supplémentaire et exposerait tous les conteneurs)

Les commentaires de

Hum lecture intéressante !

Je fonctionne sous Mac OS X et j'ai également un pare-feu local appelé LittleSnitch. Il vient d'afficher une boîte de dialogue demandant s'il était possible que 185.156.177.252 se connecte au processus com.docker.backend. J'ai cliqué sur nier.

Je me demandais comment il pouvait ouvrir un port forward dans mon routeur, mais je viens de me rendre compte que cela se fait bien entendu via UPnP ! Tous les routeurs le prennent en charge.

Ce que je me demande maintenant, c'est le pourquoi. Pourquoi quelque chose de 185.156.177.252 essaie-t-il de se connecter de l'extérieur ? Si mon processus Docker local a besoin de quelque chose, il doit appeler à la maison de l'intérieur, et non ouvrir un port à l'extérieur.

@ cpuguy83 tout va bien et dandy, mais ne change rien à cette demande.

Les gens utilisent docker et souhaitent se connecter à des applications exécutées sous docker à partir de leur système local - il s'agit d'un cas d'utilisation extrêmement courant pour les développeurs, en particulier lorsqu'ils essaient de diagnostiquer quelque chose ou écrivent un service qui n'est pas sous docker mais doit se connecter à des services hébergés dans docker (pour remplir un environnement de développement). Développer sous Docker n'est pas toujours une expérience bonne, amusante ou utile non plus, en disant :

il est recommandé d'utiliser un autre conteneur avec accès au service auquel vous souhaitez vous connecter plutôt que d'exposer un port.

Est tout simplement un non-aller. Tous les outils ne sont pas compatibles avec Docker, et ils ne devraient pas non plus l'être. L'utilisateur ne devrait pas être obligé d'être entièrement sous Docker pour utiliser les services exécutés sous Docker. Même dans ce cas, si Docker est utilisé pour faire fonctionner des serveurs, l'administrateur ne peut pas facilement les contrôler via la configuration du pare-feu et la fonctionnalité Expose Port, mais elle est orchestrée (ligne de commande, Dockerfile, Docker Compose Config) est complètement cassée.

De plus, les gens utilisent Docker Compose pour gérer une grande partie de l'environnement et spécifient via docker-compose.yml ou Dockerfile qu'un port doit être exposé afin qu'ils puissent y accéder localement. Par conséquent, dire le paramètre use the -p est incorrect car ils ne s'interfacent jamais directement avec la commande docker de manière à ce que cela fonctionne.

Exposer un port ne signifie pas que la sécurité doit être rompue. J'ai expliqué comment vous pouvez exposer un port au système local sans enfreindre la sécurité ( # 22054 (commentaire) ) et cela mettrait la gestion de l'exposition externe (hors système) entre les mains de l'utilisateur d'une manière qu'il peut facilement contrôle avec leur outillage existant.

Solution:

* Use Docker Network

* Expose the Ports on the Docker Network alone and local host

* Stop binding the port on 0.0.0.0 - or effectively doing so

* Require users to use their own firewall tooling to expose the port off system (ufw, firewalld, etc)

* Provide integrations for common firewalls to make this easy

En d'autres termes, au lieu d'accéder à un service dans un conteneur Docker via 127.0.0.1:<port> devez demander <docker container ip>:<service port> même à partir de l'hôte local. Si les utilisateurs souhaitent exposer le service hors système, ils peuvent ajouter une règle de pare-feu via leurs outils (ufw, etc.) pour transférer le port d'un port donné vers <docker container ip>:<service port> .

Vous pouvez également suivre l'approche Kubernetes avec leur conception de proxy, en faisant efficacement comme @jest suggéré dans #22054 (commentaire) . Encore une fois, cela devrait toujours être un système local jusqu'à ce que l'utilisateur l'ait délibérément exposé au réseau extérieur.

Le plus gros problème ici est que Docker compromet l'intégrité du pare-feu afin de fournir ses services, sans rien dire à l'utilisateur et sans s'intégrer dans les outils de l'utilisateur afin que l'utilisateur ne le sache ni ne puisse le contrôler.

Je me rends compte qu'il existe une tonne d'interfaces de pare-feu sur le marché - même avec iptables, il y a firewalld, ufw et une douzaine d'autres. Je ne m'attends pas vraiment à ce que Docker s'intègre avec eux (même si ce serait bien), mais je m'attends à ce que Docker fonctionne de manière à ne pas les contourner ou à briser la sécurité que l'utilisateur a configurée.

Comme cas de test de base :

* Setup a Debian-based server (Debian, Ubuntu)

* Install ufw, OpenSSH Server

* run `ufw allow OpenSSH`

* run `ufw enable`

À ce stade, vous disposez d'un serveur assez sécurisé ; le seul trafic autorisé est soit (a) lié au trafic sortant, soit (b) au trafic du serveur SSH.

Maintenant, démarrez un conteneur Docker avec un port exposé.

Une solution acceptable répondrait aux critères suivants :

* The port should not be accessible from another computer; the firewall should continue to block it from outside systems.

* The port should be accessible from the local computer (localhost).

* Multiple docker containers should be able to expose the same port and have everything work; all containers with an exposed port should be accessible from the local system only.

* The user should not have to know about their firewall configuration details to make this happen.

Essayez la même chose pour un système basé sur CentOS/Fedora/RHEL avec firewalld .

J'ai rencontré ce problème alors que j'essayais de déterminer s'il existe un moyen de configurer docker pour ne pas contourner les règles d'entrée de mon pare-feu à chaque fois qu'un port est publié. Le commentaire ci-dessus explique bien le tout et constitue également une approche plus préférable, IMO.

Hier, je suis tombé sur ce problème par hasard. J'ai passé des semaines à personnaliser méticuleusement mes règles de pare-feu iptables pour mon VPS. J'ai essayé d'être prudent et restrictif. L'entrée et la sortie du filtre sont définies par défaut. J'autorise explicitement certains trafics et bloque tout le reste. Le trafic est enregistré, et j'ai testé et surveillé.

Je savais que Docker avait apporté ses propres modifications à la chaîne Forward et à la table NAT. J'ai donc fait _extrêmement_ attention à respecter ces modifications tout au long de mon processus. De plus, tous mes conteneurs sont attachés à des réseaux définis par l'utilisateur. Le réseau hôte par défaut n'est jamais utilisé. La documentation officielle nous dit de ne pas le faire.

Ma première surprise a été que mon proxy Nginx conteneurisé était accessible à tout Internet. J'ai une option dans mes règles de pare-feu pour autoriser le trafic Web entrant en provenance du monde entier. Mais je n'avais pas encore activé cette fonction. Nulle part dans mes iptables il n'est évident que HTTP 80/443 est autorisé.

Certaines de mes applications Web reposent sur des bases de données comme MySQL. Au départ, je n'utilisais _pas_ l'option -p dans Docker Compose. Je savais que ce n'était pas nécessaire, car mon application Web et le serveur db partageaient le même réseau Docker défini par l'utilisateur. Mais en tant qu'ancien DBA, je pense toujours aux sauvegardes. J'ai donc activé l'option -p pour permettre aux tâches cron et aux outils de sauvegarde de mon

Après ma surprise chez Nginx, j'ai judicieusement décidé de vérifier que MySQL n'était pas également exposé. J'ai essayé de me connecter (depuis mon ordinateur portable) à la base de données MySQL sur mon VPS distant. Et j'ai de nouveau été choqué lorsque je me suis connecté avec succès immédiatement.

Rien de ce que je dis n'a été discuté en détail dans les messages précédents. Un grand merci à @BenjamenMeyer @jest @jacoscaz pour leur enquête et leurs suggestions utiles. Mais pour ceux qui demandent des cas d'utilisation et des expériences modernes ? Ici vous l'avez. Plus de 4 ans après le début de ce fil, des gens comme moi rencontrent toujours ce comportement. Et en repartant toujours choqué.

Oui, il existe de nombreuses solutions de contournement. Je vais en poursuivre quelques-uns immédiatement. Mais pour les mettre en œuvre, vous devez savoir que ce problème existe en premier lieu. C'est ce qui me déçoit le plus. Non pas que les développeurs de Docker aient pris certaines décisions de conception, qu'elles soient bonnes ou mauvaises.

Mais que ce "comportement de contournement du pare-feu" n'est pas clairement signalé, fortement averti et plus largement documenté. En matière de sécurité réseau, les surprises ne sont jamais une bonne chose.

Je voulais partager ma solution de contournement pour ce problème au cas où d'autres le trouveraient utile. Utiliser iptables et ipset. Testé sur Ubuntu, CentOS 7 & RHEL 7. ipset est utilisé car les IP "de confiance" ne sont pas toujours dans la même plage IP (limitation fixe d'iptables).
Il fonctionne avec Docker normal et Docker Swarm (alias SwarmKit).
Il garantit que vous êtes sécurisé par défaut, en autorisant uniquement les adresses IP que vous spécifiez pour pouvoir se connecter aux ports du système d'exploitation et aux ports Docker (en utilisant iptables et ipset) que vous spécifiez doivent être ouverts. A également la possibilité de rendre un port OS ou un port Docker "public" (ouvert à toutes les IP)

Ansible n'est pas obligatoire, mais facilite les choses. Rôle Ansible : https://github.com/ryandaniels/ansible-role-iptables-docker
Étapes manuelles pour CentOS/RHEL :
https://github.com/ryandaniels/ansible-role-iptables-docker#manual-commands-centosrhel
Et Ubuntu 18.04/20.04 :
https://github.com/ryandaniels/ansible-role-iptables-docker#manual-commands-ubuntu-2004

Vous devrez installer/configurer ipset en plus d'iptables (voir les liens ci-dessus en cas de problème).
Exemple de configuration iptables (avec le port ssh 22 ouvert à tous) :

*filter
:DOCKER-USER - [0:0]
:FILTERS - [0:0]
#Can't flush INPUT. wipes out docker swarm encrypted overlay rules
#-F INPUT
#Use ansible or run manually once instead to add -I INPUT -j FILTERS
#-I INPUT -j FILTERS
-A DOCKER-USER -m state --state RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -i docker_gwbridge -j RETURN
-A DOCKER-USER -s 172.18.0.0/16 -j RETURN
-A DOCKER-USER -i docker0 -j RETURN
-A DOCKER-USER -s 172.17.0.0/16 -j RETURN
#Below Docker ports open to everyone if uncommented
#-A DOCKER-USER -p tcp -m tcp -m multiport --dports 8000,8001 -j RETURN
#-A DOCKER-USER -p udp -m udp -m multiport --dports 9000,9001 -j RETURN
-A DOCKER-USER -m set ! --match-set ip_allow src -j DROP
-A DOCKER-USER -j RETURN
-F FILTERS
#Because Docker Swarm encrypted overlay network just appends rules to INPUT
-A FILTERS -p udp -m policy --dir in --pol ipsec -m udp --dport 4789 -m set --match-set ip_allow src -j RETURN
-A FILTERS -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FILTERS -p icmp -j ACCEPT
-A FILTERS -i lo -j ACCEPT
#Below OS ports open to everyone if uncommented
-A FILTERS -p tcp -m state --state NEW -m tcp -m multiport --dports 22 -j ACCEPT
#-A FILTERS -p udp -m udp -m multiport --dports 53,123 -j ACCEPT
-A FILTERS -m set ! --match-set ip_allow src -j DROP
-A FILTERS -j RETURN
COMMIT
Cette page vous a été utile?
0 / 5 - 0 notes