italia / spid-saml-check

Tool di verifica implementazione SPID SAML
European Union Public License 1.2
71 stars 59 forks source link

L'intero server crasha quando si inserisce un url che punta all'IP di loopback #282

Open gabrielesilinic opened 3 weeks ago

gabrielesilinic commented 3 weeks ago

non mi è pienamente chiaro che stia succedendo, ma sto cercando di testare l'applicazione con italia/spid-keycloak-provider. a causa del fatto che come da pull request #276 non sia possibile usare localhost ho semplicemente cercato di bypassare il problema usando l'IP localhost corrispondente per registrare il mio SP dentro il server. il problema è che appena metto http://127.0.0.1:8080/my-url semplicemente muore, sotto il messaggio d'errore che ho recuperato dal container docker prima che terminasse.

>>> 2024-06-12 17:17:03 - POST [] /api/metadata-sp/download
events.js:377
      throw er; // Unhandled 'error' event
      ^

Error: connect ECONNREFUSED 127.0.0.1:8080
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1159:16)
Emitted 'error' event on ClientRequest instance at:
    at Socket.socketErrorListener (_http_client.js:475:9)
    at Socket.emit (events.js:400:28)
    at emitErrorNT (internal/streams/destroy.js:106:8)
    at emitErrorCloseNT (internal/streams/destroy.js:74:3)
    at processTicksAndRejections (internal/process/task_queues.js:82:21) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 8080
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! spid-validator@1.0.0 start-prod: `node server/spid-validator.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the spid-validator@1.0.0 start-prod script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2024-06-12T15_17_03_841Z-debug.log

qui sotto per comodità il dockerfile che si occupa di far andare keycloak con tanto di plugin. anche se onestamente dubito che serva perché comunque è possibile che crashi prima che a keycloak importi qualcosa.

# WARNING: this dockerfile at this point in time is meant to be for development purposes only.
# please modify it to make it safe it for production use.

# Stage 1: Build the Keycloak plugin
# We use a multi-stage build to separate the build environment from the runtime environment.
# This keeps the final image smaller and only includes necessary runtime dependencies.

# Use Maven with OpenJDK to build the plugin
FROM maven:3.8.6-openjdk-18-slim AS builder

# Install Git, we don't have it by default.
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*

WORKDIR /opt/spid-keycloak-provider

# Clone the repository and checkout the specific commit matching the supported version of keycloak
RUN git clone https://github.com/italia/spid-keycloak-provider.git . \
    && git checkout 57bd897e9610c4ddb570a38b8f105eec02ec157e

# Build the project using Maven
RUN mvn clean package

# Stage 2: Use the Quay Keycloak image and copy the plugin
# By using a multi-stage build, we ensure that the final image only contains the necessary runtime components.
# This approach helps in creating lightweight and secure containers.

# Use the official Quay Keycloak image (version 24) as this is what that plugin at that point in time supports.
FROM quay.io/keycloak/keycloak:24.0.0

WORKDIR /opt/keycloak

# Copy the built plugin from the builder stage
COPY --from=builder /opt/spid-keycloak-provider/target/spid-provider.jar /opt/keycloak/providers/

# Expose necessary ports for Keycloak
EXPOSE 8080 8443

# Set environment variables for Keycloak admin user
ENV KEYCLOAK_ADMIN=admin
ENV KEYCLOAK_ADMIN_PASSWORD=admin

# Set environment variable for development mode
ENV KC_DEV=true

# Entry point to start Keycloak in development mode
ENTRYPOINT ["./bin/kc.sh", "start-dev"]

nota: l'immagine non si occupa della completa configurazione del endpoint saml --> openid connect SPID, per quello si può dare un occhiata in wiki.

fume commented 3 weeks ago

Dall'interno del container, di default, 127.0.0.1 non fa riferimento al "tuo" localhost (ovvero il Docker host), ma al localhost del container stesso, motivo per cui ottieni l'errore ECONNREFUSED, visto che all'interno del tuo container nessuno è in ascolto su quella porta/ip. L'errore causa poi il crash del container.

Se vuoi che il tuo container sia in grado di comunicare con l'host, hai diverse opzioni ma dipendono dal networking mode di Docker che stai usando.

Se non hai specificato il parametro --network quando hai lanciato il container, allora stai utilizzando quasi sicuramente la modalità "bridge" e il tuo container è stato collegato alla docker network di nome "bridge". In questo caso per parlare con l'host devi usare il gateway IP della docker network "bridge".

C:\> docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
4137fed6027c   bridge    bridge    local
7b01931d1691   host      host      local
ab8fd4415ad0   none      null      local
C:\> docker network inspect bridge
....
....
"IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1" <---- usando questo IP dal container, parlerai con l'host
                }
            ]
        }
....

Nel mio caso l'IP da utilizzare è 172.17.0.1.

In alternativa puoi lanciare il container con il parametro --network="host" e in questo caso anche dall'interno del container "localhost" o 127.0.0.1 faranno riferimento all'host e non al container stesso. Questo approccio è spesso sconsigliato in quanto il container stesso potrebbe usare "localhost" aspettandosi di parlare con se stesso, ma in questo caso parlerebbe con l'host, portandi quindi ad altri tipi di errori.

In ogni caso, se anche spid-keycloak-provider (non ho visto il repo, non so di cosa si tratta 😄) è stato lanciato tramite docker (senza parametro --network che equivale a --network="bridge"), allora entrambi i container saranno collegati alla network "bridge", motivo per cui potranno parlarsi usando l'ip del container. Gli ip dei container potrai recuperarli con il comando

C:\> docker network inspect bridge
.....
......
"Containers": {
            "0c59a21e641ad67c5b14435b6f72d97aa31fae7527a8f6dc381e0d33ae291af2": {
                "Name": "container2",
                "EndpointID": "7747a9007eace05fef1a876735551fb738e36cc11214cbf56acf1f1dc9575818",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16", <--------- 172.17.0.3 è l'IP del container2
                "IPv6Address": ""
            },
            "56a70c8dee9406365648d09a35df9483c59533a0c72bd447695cb61a17607a80": {
                "Name": "container1",
                "EndpointID": "788231f2767e37d625b555c5e7a1740fb6f4f4879425ecba002131500b9df8d4",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16", <--------- 172.17.0.2 è l'IP del container1
                "IPv6Address": ""
            }
        }
......

Alternativa forse più semplice è usare docker-compose per lanciare entrambi i container. In questo caso i container potranno parlarsi fra di loro semplicemente usando il service name che gli hai assegnato, in quanto verranno collegaati ad una network creata ad-hoc in fase di avvio.

Alternativa simile al docker-compose: creare una user defined bridge network e collegarci esplicitamente i containers. In questo modo i container potranno parlarsi tramite IP e/o tramite nome container (cosa non possibile sulla rete predefinita chiamata "bridge").

C:\Users\fumer> docker network create my-network
C:\Users\fumer> docker run -d --rm --name container1 --network my-network alpine /bin/sh -c "sleep infinity"
C:\Users\fumer> docker run -d --rm --name container2 --network my-network alpine /bin/sh -c "sleep infinity"
C:\Users\fumer> docker exec -it container1 /bin/sh
/ # ping container2
PING container2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.049 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.081 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.059 ms
64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.059 ms
64 bytes from 172.18.0.2: seq=4 ttl=64 time=0.058 ms
--- container2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.049/0.061/0.081 ms
C:\Users\fumer> docker exec -it container2 /bin/sh
/ # ping container1
PING container1 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.060 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.060 ms
64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.099 ms
64 bytes from 172.18.0.3: seq=3 ttl=64 time=0.067 ms
64 bytes from 172.18.0.3: seq=4 ttl=64 time=0.057 ms
64 bytes from 172.18.0.3: seq=5 ttl=64 time=0.103 ms
64 bytes from 172.18.0.3: seq=6 ttl=64 time=0.097 ms
64 bytes from 172.18.0.3: seq=7 ttl=64 time=0.059 ms
^C
--- container1 ping statistics ---
8 packets transmitted, 8 packets received, 0% packet loss
round-trip min/avg/max = 0.057/0.075/0.103 ms
gabrielesilinic commented 3 weeks ago

@fume grazie per il ripasso, onestamente ero troppo preso per pensare a questa cosa (anche se ne conoscevo l'esistenza). è davvero da un po' che sto cercando di far funzionare SPID con una Single Page Application via OIDC.

un sacco di tool sono o inesistenti o rotti. In ogni caso al momento sono riuscito ad arrivare alla schermata di login spid semplicemente configurando a keycloak per usare keycloak.localhost. in questo modo keycloak accetterà connessioni sotto quel nome e spid-saml-check non sfracassa le balle per qualsivoglia ragione. plus ogni dominio .localhost è considerato un secure context dai browser e ciò consente una serie di feature altrimenti non disponibili.

ENV KC_HOSTNAME=keycloak.localhost

ora, non è abbastanza per completare il flow keycloak e SPID. ma cercherò di capire come farlo.