Skip to content

Instantly share code, notes, and snippets.

@paoloantinori
Last active June 11, 2020 08:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paoloantinori/a6332243186aa9e8004cf1cf46335206 to your computer and use it in GitHub Desktop.
Save paoloantinori/a6332243186aa9e8004cf1cf46335206 to your computer and use it in GitHub Desktop.
How to protect Apicurio Registry (or any webapp) with Louketo Reverse Proxy, without modifying the code of your app.

how to protect Apicurio Registry with Louketo Reverse Proxy

# download the 3 servers we are going to use
podman pull quay.io/keycloak/keycloak
podman pull apicurio/apicurio-registry-mem

# louketo hasn't released a OCI image yet
# assuming you are on Linux. If not download the correct image
curl --remote-name --location https://github.com/louketo/louketo-proxy/releases/download/1.0.0-alpha.1/louketo-proxy_1.0.0-alpha.1_linux_amd64.tar.gz
# curl --remote-name --location https://github.com/louketo/louketo-proxy/releases/download/1.0.0-alpha.1/louketo-proxy_1.0.0-alpha.1_macOS_amd64.tar.gz

tar -xvf louketo-proxy* --strip-components 1


# bring up apicurio, exposing port 8081
podman run -p 8081:8080 -it apicurio/apicurio-registry-mem # listen on port 8081 on host

# in another shell
# create local document to import in Service Registry
cat > sample_document_for_sr.json <<"EOF"
{
  "openapi": "3.0.2",
  "info": {
    "title": "Empty API",
    "version": "1.0.7",
    "description": "An example API design using OpenAPI."
  },
  "paths": {
    "/widgets": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            },
            "description": "All widgets"
          }
        },
        "summary": "Get widgets"
      }
    }
  },
  "components": {
    "schemas": {
      "Widget": {
        "title": "Root Type for Widget",
        "description": "A sample data type.",
        "type": "object",
        "properties": {
          "property-1": {
            "type": "string"
          },
          "property-2": {
            "type": "boolean"
          }
        },
        "example": {
          "property-1": "value1",
          "property-2": true
        }
      }
    }
  }
}
EOF

# query artifact (expect an empty answer)
curl http://localhost:8081/api/artifacts

# upload resource
curl -v -X POST -H "Content-Type: application/json; artifactType=AVRO" -F filename=@"sample_document_for_sr.json;type=application/json; artifactType=JSON" "http://localhost:8081/api/artifacts"

# query the unprotected endpoint resource (expect an entry)
curl http://localhost:8081/api/artifacts


# create configuration for keycloak realm
cat > demorealm.json <<"EOF"
{
    "realm": "sample_realm",
    "enabled": true,
    "accessTokenLifespan": 60,
    "accessCodeLifespan": 60,
    "accessCodeLifespanUserAction": 300,
    "ssoSessionIdleTimeout": 600,
    "ssoSessionMaxLifespan": 36000,
    "sslRequired": "external",
    "registrationAllowed": false,
    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
    "requiredCredentials": [ "password" ],
    "users" : [
        {
            "username" : "alice",
            "enabled": true,
            "email" : "alice@keycloak.org",
            "firstName": "Alice",
            "lastName": "Liddel",
            "credentials" : [
                { "type" : "password",
                    "value" : "password" }
            ],
            "realmRoles": [ "user", "offline_access"  ],
            "clientRoles": {
                "account": [ "manage-account" ]
            }
        },
        {
            "username" : "test-admin",
            "enabled": true,
            "email" : "test@admin.org",
            "firstName": "Admin",
            "lastName": "Test",
            "credentials" : [
                { "type" : "password",
                    "value" : "password" }
            ],
            "realmRoles": [ "user","admin" ],
            "clientRoles": {
                "realm-management": [ "realm-admin" ],
                "account": [ "manage-account" ]
            }
        }
    ],
    "roles" : {
        "realm" : [
            {
                "name": "user",
                "description": "User privileges"
            },
            {
                "name": "admin",
                "description": "Administrator privileges"
            }
        ]
    },
    "clients": [
        {
        "clientId" : "public",
        "enabled" : true,
        "publicClient": true,
        "directAccessGrantsEnabled": true,
        "protocolMappers": [
            {
            "protocolMapper" : "oidc-audience-mapper",
            "protocol" : "openid-connect",
            "name" : "gatekeeper",
            "config" : {
                "id.token.claim": "false",
                "access.token.claim": "true",
                "included.client.audience": "gatekeeper"
                }
            }
            ]
        },
        {
        "clientId" : "gatekeeper",
        "enabled" : true,
        "secret" : "d0b8122f-8dfb-46b7-b68a-f5cc4e25d000",
        "protocolMappers": [
            {
            "protocolMapper" : "oidc-audience-mapper",
            "protocol" : "openid-connect",
            "name" : "gatekeeper",
            "config" : {
                "id.token.claim": "false",
                "access.token.claim": "true",
                "included.client.audience": "gatekeeper"
                }
            }
            ],
        "redirectUris": [
            "http://localhost:8083/oauth/callback"
            ]
        }
    ]
}
EOF



# run an instance of keycloak with preconfigured realms on the host directly on the host address (so that we can use "localhost" from the various container to reach it)
podman run \
  --net=host \
  --rm \
  --name kc \
  -v .:/opt/realms:Z \
  -e KEYCLOAK_USER=admin \
  -e KEYCLOAK_PASSWORD=admin \
  -e KEYCLOAK_IMPORT="/opt/realms/demorealm.json" \
  quay.io/keycloak/keycloak \
    -Djboss.bind.address.private=localhost \
    -Djboss.bind.address=localhost

# wait until Keycloak is up!

# in another shell
# create config for louketo
cat > louketo.yaml <<"EOF"
# is the url for retrieve the OpenID configuration - normally the <server>/auth/realm/<realm_name>
discovery-url: http://localhost:8080/auth/realms/sample_realm
# the client id for the 'client' application
client-id: gatekeeper
# the secret associated to the 'client' application
client-secret: d0b8122f-8dfb-46b7-b68a-f5cc4e25d000
# the interface definition you wish the proxy to listen, all interfaces is specified as ':<port>', unix sockets as unix://<REL_PATH>|</ABS PATH>
listen: :8083
# whether to enable refresh tokens
enable-refresh-tokens: true
# the location of a certificate you wish the proxy to use for TLS support
tls-cert:
# the location of a private key for TLS
tls-private-key:
# the redirection url, essentially the site url, note: /oauth/callback is added at the end
redirection-url: http://localhost:8083
# the encryption key used to encode the session state
encryption-key: 1122334455667788
secure-cookie: false
# the upstream endpoint which we should proxy request
upstream-url: http://localhost:8081/api
# additional scopes to add to the default (openid+email+profile)
# scopes:
# - vpn-user
# a collection of resource i.e. urls that you wish to protect
resources:
- uri: /*
  # the methods on this url that should be protected, if missing, we assuming all
  methods:
  - GET
  # a list of roles the user must have in order to access urls under the above
  # If all you want is authentication ONLY, simply remove the roles array - the user must be authenticated but
  # no roles are required
  roles:
  - user
EOF

# run louketo reverse proxy
./louketo-proxy --config louketo.yaml

# in another shell
# use direct access grant (password based) method to get a valid Bearer token
RESULT=$(curl --data "grant_type=password&client_id=public&username=alice&password=password" http://localhost:8080/auth/realms/sample_realm/protocol/openid-connect/token)
TOKEN=$(echo $RESULT | sed 's/.*access_token":"//g' | sed 's/".*//g')

# this will fail (shows a redirect to authentication link)
curl http://localhost:8083/api/artifacts

# this will work since it's using the token we got earlier
curl http://localhost:8083/api/artifacts -H "Accept: application/json" -H "Authorization: Bearer $TOKEN"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment