fix: JupyterHub Dockerfile - add eric user, sudo, fix DB path (4 slashes)
This commit is contained in:
6
configuration/ditto/Dockerfile
Normal file
6
configuration/ditto/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM eclipse/ditto-gateway:latest
|
||||
|
||||
USER root
|
||||
# Copy the modified JAR (with open auth in reference.conf)
|
||||
COPY ditto-gateway-service-3.8.12-allinone.jar /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar
|
||||
USER ditto
|
||||
22
configuration/ditto/application.conf
Normal file
22
configuration/ditto/application.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
# Minimal override - authentication settings only
|
||||
# This file is loaded after reference.conf by Typesafe config
|
||||
|
||||
ditto {
|
||||
gateway {
|
||||
authentication {
|
||||
pre-authentication {
|
||||
enabled = true
|
||||
}
|
||||
devops {
|
||||
secured = false
|
||||
devops-authentication-method = "basic"
|
||||
password = "ditto-devops-secret"
|
||||
password = ${?DEVOPS_PASSWORD}
|
||||
status-secured = false
|
||||
status-authentication-method = "basic"
|
||||
statusPassword = "ditto-status-secret"
|
||||
statusPassword = ${?STATUS_PASSWORD}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
configuration/ditto/ditto-gateway-service-3.8.12-allinone.jar
Normal file
BIN
configuration/ditto/ditto-gateway-service-3.8.12-allinone.jar
Normal file
Binary file not shown.
7
configuration/ditto/docker-entrypoint.sh
Normal file
7
configuration/ditto/docker-entrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Fix permissions for mounted config files
|
||||
if [ -f /opt/ditto/gateway-extension.conf ]; then
|
||||
chmod 644 /opt/ditto/gateway-extension.conf
|
||||
fi
|
||||
# Start the gateway
|
||||
exec java -jar /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar
|
||||
2
configuration/ditto/entrypoint.sh
Normal file
2
configuration/ditto/entrypoint.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec java -Dconfig.file=/opt/ditto/gateway.conf -jar /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar
|
||||
76
configuration/ditto/fix-jar.sh
Normal file
76
configuration/ditto/fix-jar.sh
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
WORKDIR=/tmp/ditto-jar-mod
|
||||
rm -rf $WORKDIR && mkdir -p $WORKDIR
|
||||
|
||||
echo "=== Extracting JAR ==="
|
||||
cd $WORKDIR
|
||||
docker run --rm eclipse/ditto-gateway:latest cat /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar > ditto-gateway-service-3.8.12-allinone.jar
|
||||
jar xf ditto-gateway-service-3.8.12-allinone.jar reference.conf
|
||||
|
||||
echo "=== Original reference.conf tail ==="
|
||||
tail -5 reference.conf
|
||||
|
||||
echo "=== Adding auth config to reference.conf ==="
|
||||
# Remove the last closing brace, add our config, re-add the closing brace
|
||||
# The reference.conf ends with nested braces - find the very last line
|
||||
python3 << 'PYEOF'
|
||||
with open("/tmp/ditto-jar-mod/reference.conf", "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Find the last non-empty line that is just a closing brace
|
||||
# We need to insert our config before the outermost closing brace
|
||||
|
||||
# Simple approach: append before the very last }
|
||||
# Count total closing braces at the end
|
||||
content = "".join(lines)
|
||||
|
||||
# The reference.conf has a complex nested structure
|
||||
# We'll add our ditto config as a new root-level block at the end
|
||||
# We need to close the last block and add a comma, then our new block
|
||||
|
||||
# Actually, simpler: just append if the file ends with }
|
||||
# Find the position of the very last }
|
||||
last_brace_pos = content.rfind('}')
|
||||
if last_brace_pos >= 0:
|
||||
# Check if there's content after the last } (like kamon.conf include)
|
||||
rest = content[last_brace_pos+1:].strip()
|
||||
if rest:
|
||||
# There's content after the last }, our approach is wrong
|
||||
print(f"Content after last }}: {rest[:100]}")
|
||||
# Insert before the last } with a comma
|
||||
new_content = content[:last_brace_pos].rstrip()
|
||||
# Remove trailing comma if present
|
||||
if new_content.endswith(','):
|
||||
new_content = new_content[:-1]
|
||||
new_content += ',\n\n# Custom auth overrides\n' + rest[:0] + '\n' + ' authentication {\n pre-authentication {\n enabled = true\n }\n devops {\n secured = false\n }\n }\n}\n'
|
||||
# Just append
|
||||
new_content = content.rstrip() + '\n\n# Custom auth overrides\nditto {\n gateway {\n authentication {\n pre-authentication {\n enabled = true\n }\n devops {\n secured = false\n }\n }\n }\n}\n'
|
||||
else:
|
||||
# Last char is }, replace it with our config + }
|
||||
new_content = content[:last_brace_pos].rstrip()
|
||||
# Remove trailing comma
|
||||
if new_content.endswith(','):
|
||||
new_content = new_content[:-1]
|
||||
new_content += ',\n\n# Custom auth overrides\n gateway {\n authentication {\n pre-authentication {\n enabled = true\n }\n devops {\n secured = false\n }\n }\n }\n}\n'
|
||||
else:
|
||||
new_content = content
|
||||
|
||||
with open("/tmp/ditto-jar-mod/reference.conf", "w") as f:
|
||||
f.write(new_content)
|
||||
|
||||
print("reference.conf modified successfully")
|
||||
PYEOF
|
||||
|
||||
echo "=== Modified reference.conf tail ==="
|
||||
tail -20 reference.conf
|
||||
|
||||
echo "=== Updating JAR (replacing reference.conf) ==="
|
||||
jar uf ditto-gateway-service-3.8.12-allinone.jar reference.conf
|
||||
|
||||
echo "=== Verifying modified reference.conf in JAR ==="
|
||||
jar xf ditto-gateway-service-3.8.12-allinone.jar reference.conf
|
||||
grep -c "pre-authentication" reference.conf || echo "NOT FOUND in JAR"
|
||||
|
||||
echo "=== Done. JAR is ready at $WORKDIR ==="
|
||||
7
configuration/ditto/gateway-extension.conf
Normal file
7
configuration/ditto/gateway-extension.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
ditto {
|
||||
gateway {
|
||||
http {
|
||||
enablecors = true
|
||||
}
|
||||
}
|
||||
}
|
||||
363
configuration/ditto/gateway.conf
Normal file
363
configuration/ditto/gateway.conf
Normal file
@@ -0,0 +1,363 @@
|
||||
ditto {
|
||||
version = "3.8.12"
|
||||
|
||||
extensions {
|
||||
jwt-authorization-subjects-provider = {
|
||||
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DittoJwtAuthorizationSubjectsProvider
|
||||
}
|
||||
jwt-authentication-result-provider = {
|
||||
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DefaultJwtAuthenticationResultProvider
|
||||
extension-config = {
|
||||
role = regular
|
||||
jwt-authorization-subjects-provider = {
|
||||
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DittoJwtAuthorizationSubjectsProvider
|
||||
extension-config = {
|
||||
role = regular
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jwt-authentication-result-provider-devops = {
|
||||
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DefaultJwtAuthenticationResultProvider
|
||||
extension-config = {
|
||||
role = devops
|
||||
jwt-authorization-subjects-provider = {
|
||||
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DittoJwtAuthorizationSubjectsProvider
|
||||
extension-config = {
|
||||
role = devops
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
signal-enrichment-provider {
|
||||
extension-class = org.eclipse.ditto.gateway.service.endpoints.utils.DefaultGatewaySignalEnrichmentProvider
|
||||
extension-config = {
|
||||
cache {
|
||||
enabled = true
|
||||
maximum-size = 20000
|
||||
expire-after-create = 2m
|
||||
}
|
||||
}
|
||||
}
|
||||
http-bind-flow-provider = org.eclipse.ditto.gateway.service.endpoints.routes.LoggingHttpBindFlowProvider
|
||||
websocket-config-provider = org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpWebSocketConfigProvider
|
||||
gateway-authentication-directive-factory = org.eclipse.ditto.gateway.service.endpoints.directives.auth.DittoGatewayAuthenticationDirectiveFactory
|
||||
http-request-actor-props-factory = org.eclipse.ditto.gateway.service.endpoints.actors.DefaultHttpRequestActorPropsFactory
|
||||
sse-event-sniffer = org.eclipse.ditto.gateway.service.endpoints.routes.sse.NoOpSseEventSniffer
|
||||
streaming-authorization-enforcer = org.eclipse.ditto.gateway.service.streaming.NoOpAuthorizationEnforcer
|
||||
incoming-websocket-event-sniffer = org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpIncomingWebSocketEventSniffer
|
||||
outgoing-websocket-event-sniffer = org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpOutgoingWebSocketEventSniffer
|
||||
custom-api-routes-provider = org.eclipse.ditto.gateway.service.endpoints.routes.NoopCustomApiRoutesProvider
|
||||
sse-connection-supervisor = org.eclipse.ditto.gateway.service.endpoints.routes.sse.NoOpSseConnectionSupervisor
|
||||
websocket-connection-supervisor = "org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpWebSocketSupervisor"
|
||||
connections-retrieval-actor-props-factory = org.eclipse.ditto.gateway.service.endpoints.actors.DefaultConnectionsRetrievalActorPropsFactory
|
||||
}
|
||||
|
||||
service-name = "gateway"
|
||||
mapping-strategy.implementation = "org.eclipse.ditto.gateway.service.util.GatewayMappingStrategies"
|
||||
|
||||
gateway {
|
||||
http {
|
||||
hostname = ""
|
||||
hostname = ${?HOSTNAME}
|
||||
hostname = ${?BIND_HOSTNAME}
|
||||
port = 8080
|
||||
port = ${?HTTP_PORT}
|
||||
port = ${?PORT}
|
||||
coordinated-shutdown-timeout = 65s
|
||||
coordinated-shutdown-timeout = ${?COORDINATED_SHUTDOWN_REQUEST_TIMEOUT}
|
||||
schema-versions = [2]
|
||||
protocol-headers = ["X-Forwarded-Proto", "x_forwarded_proto"]
|
||||
forcehttps = false
|
||||
forcehttps = ${?FORCE_HTTPS}
|
||||
redirect-to-https = false
|
||||
redirect-to-https = ${?REDIRECT_TO_HTTPS}
|
||||
redirect-to-https-blocklist-pattern = "/api.*|/ws.*|/status.*|/overall.*"
|
||||
enablecors = false
|
||||
enablecors = ${?ENABLE_CORS}
|
||||
request-timeout = 60s
|
||||
request-timeout = ${?REQUEST_TIMEOUT}
|
||||
additional-accepted-media-types = ${?ADDITIONAL_ACCEPTED_MEDIA_TYPES}
|
||||
query-params-as-headers = [
|
||||
"accept"
|
||||
"channel"
|
||||
"correlation-id"
|
||||
"requested-acks"
|
||||
"declared-acks"
|
||||
"response-required"
|
||||
"timeout"
|
||||
"live-channel-timeout-strategy"
|
||||
"allow-policy-lockout"
|
||||
"condition"
|
||||
"live-channel-condition"
|
||||
"at-historical-revision"
|
||||
"at-historical-timestamp"
|
||||
"dry-run"
|
||||
]
|
||||
}
|
||||
|
||||
streaming {
|
||||
session-counter-scrape-interval = 30s
|
||||
parallelism = 64
|
||||
parallelism = ${?GATEWAY_STREAMING_PARALLELISM}
|
||||
search-idle-timeout = 60s
|
||||
search-idle-timeout = ${?GATEWAY_STREAMING_SEARCH_IDLE_TIMEOUT}
|
||||
subscription-refresh-delay = 5m
|
||||
subscription-refresh-delay = ${?GATEWAY_STREAMING_SUBSCRIPTION_REFRESH_DELAY}
|
||||
acknowledgement {
|
||||
forwarder-fallback-timeout = 65s
|
||||
}
|
||||
websocket {
|
||||
subscriber {
|
||||
backpressure-queue-size = 100
|
||||
}
|
||||
publisher {
|
||||
backpressure-buffer-size = 200
|
||||
}
|
||||
throttling-rejection-factor = 1.25
|
||||
throttling {
|
||||
enabled = false
|
||||
}
|
||||
streaming-authorization-enforcer = "org.eclipse.ditto.gateway.service.streaming.NoOpAuthorizationEnforcer"
|
||||
}
|
||||
sse {
|
||||
throttling {
|
||||
enabled = false
|
||||
}
|
||||
streaming-authorization-enforcer = "org.eclipse.ditto.gateway.service.streaming.NoOpAuthorizationEnforcer"
|
||||
}
|
||||
}
|
||||
|
||||
command {
|
||||
default-timeout = ${ditto.gateway.http.request-timeout}
|
||||
max-timeout = 1m
|
||||
smart-channel-buffer = 10s
|
||||
connections-retrieve-limit = 100
|
||||
}
|
||||
message {
|
||||
default-timeout = 10s
|
||||
max-timeout = 1m
|
||||
}
|
||||
claim-message {
|
||||
default-timeout = 1m
|
||||
max-timeout = 10m
|
||||
}
|
||||
dns {
|
||||
address = none
|
||||
address = ${?DNS_SERVER}
|
||||
}
|
||||
|
||||
authentication {
|
||||
http {
|
||||
proxy {
|
||||
enabled = false
|
||||
enabled = ${?AUTH_HTTP_PROXY_ENABLED}
|
||||
hostname = ${?AUTH_HTTP_PROXY_HOST}
|
||||
port = ${?AUTH_HTTP_PROXY_PORT}
|
||||
username = ${?AUTH_HTTP_PROXY_USERNAME}
|
||||
password = ${?AUTH_HTTP_PROXY_PASSWORD}
|
||||
}
|
||||
}
|
||||
oauth {
|
||||
protocol = "https"
|
||||
protocol = ${?OAUTH_PROTOCOL}
|
||||
allowed-clock-skew = 10s
|
||||
allowed-clock-skew = ${?OAUTH_ALLOWED_CLOCK_SKEW}
|
||||
openid-connect-issuers = {
|
||||
google = {
|
||||
issuer = "accounts.google.com"
|
||||
}
|
||||
}
|
||||
token-integration-subject = "integration:{{policy-entry:label}}:{{jwt:aud}}"
|
||||
token-integration-subject = ${?OAUTH_TOKEN_INTEGRATION_SUBJECT}
|
||||
}
|
||||
|
||||
# PRE-AUTHENTICATION = open access for /api/2/
|
||||
pre-authentication {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
devops {
|
||||
secured = false
|
||||
devops-authentication-method = "basic"
|
||||
password = "ditto-devops-secret"
|
||||
password = ${?DEVOPS_PASSWORD}
|
||||
status-secured = false
|
||||
status-authentication-method = "basic"
|
||||
statusPassword = "ditto-status-secret"
|
||||
statusPassword = ${?STATUS_PASSWORD}
|
||||
}
|
||||
}
|
||||
|
||||
health-check {
|
||||
enabled = true
|
||||
enabled = ${?HEALTH_CHECK_ENABLED}
|
||||
interval = 60s
|
||||
interval = ${?HEALTH_CHECK_INTERVAL}
|
||||
service.timeout = 10s
|
||||
service.timeout = ${?HEALTH_CHECK_SERVICE_TIMEOUT}
|
||||
cluster-roles = {
|
||||
enabled = true
|
||||
enabled = ${?HEALTH_CHECK_ROLES_ENABLED}
|
||||
expected = [
|
||||
"policies"
|
||||
"things"
|
||||
"search"
|
||||
"gateway"
|
||||
"connectivity"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public-health {
|
||||
cache-timeout = 20s
|
||||
cache-timeout = ${?GATEWAY_STATUS_HEALTH_EXTERNAL_TIMEOUT}
|
||||
}
|
||||
|
||||
cloud-events {
|
||||
empty-schema-allowed = true
|
||||
data-types = [
|
||||
"application/json"
|
||||
"application/vnd.eclipse.ditto+json"
|
||||
]
|
||||
}
|
||||
|
||||
cache {
|
||||
publickeys {
|
||||
maxentries = 32
|
||||
expiry = 60m
|
||||
maximum-size = ${ditto.gateway.cache.publickeys.maxentries}
|
||||
expire-after-write = ${ditto.gateway.cache.publickeys.expiry}
|
||||
}
|
||||
}
|
||||
|
||||
statistics {
|
||||
ask-timeout = 5s
|
||||
ask-timeout = ${?STATISTICS_UPDATE_INTERVAL}
|
||||
update-interval = 15s
|
||||
update-interval = ${?STATISTICS_UPDATE_INTERVAL}
|
||||
details-expire-after = 3s
|
||||
details-expire-after = ${?STATISTICS_DETAILS_EXPIRE_AFTER}
|
||||
shards = [
|
||||
{
|
||||
region = "thing"
|
||||
role = "things"
|
||||
root = "/user/thingsRoot"
|
||||
}
|
||||
{
|
||||
region = "policy"
|
||||
role = "policies"
|
||||
root = "/user/policiesRoot"
|
||||
}
|
||||
{
|
||||
region = "search-wildcard-updater"
|
||||
role = "search"
|
||||
root = "/user/thingsWildcardSearchRoot/searchUpdaterRoot"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
tracing {
|
||||
filter = {
|
||||
includes = ["**"]
|
||||
excludes = ["GET /ws/2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
secrets {
|
||||
devops_password {
|
||||
name = "devops_password"
|
||||
name = ${?DEVOPS_PASSWORD_NAME}
|
||||
}
|
||||
status_password {
|
||||
name = "status_password"
|
||||
name = ${?STATUS_PASSWORD_NAME}
|
||||
}
|
||||
}
|
||||
|
||||
pekko.http.client {
|
||||
user-agent-header = eclipse-ditto/${ditto.version}
|
||||
}
|
||||
|
||||
pekko {
|
||||
actor {
|
||||
default-dispatcher {
|
||||
executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
|
||||
}
|
||||
deployment {
|
||||
/gatewayRoot/proxy {
|
||||
router = round-robin-pool
|
||||
resizer {
|
||||
lower-bound = 5
|
||||
upper-bound = 100
|
||||
messages-per-resize = 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cluster {
|
||||
sharding {
|
||||
role = ${ditto.service-name}
|
||||
passivation {
|
||||
strategy = "off"
|
||||
}
|
||||
}
|
||||
roles = ["gateway"]
|
||||
}
|
||||
coordinated-shutdown {
|
||||
phases {
|
||||
service-requests-done {
|
||||
timeout = 70s
|
||||
}
|
||||
}
|
||||
}
|
||||
http {
|
||||
server {
|
||||
server-header = ""
|
||||
request-timeout = ${ditto.gateway.http.request-timeout}
|
||||
idle-timeout = 610s
|
||||
max-connections = 4096
|
||||
raw-request-uri-header = on
|
||||
parsing {
|
||||
max-uri-length = 8k
|
||||
max-content-length = 1m
|
||||
uri-parsing-mode = relaxed
|
||||
}
|
||||
websocket {
|
||||
periodic-keep-alive-mode = ping
|
||||
periodic-keep-alive-max-idle = 30s
|
||||
}
|
||||
termination-deadline-exceeded-response {
|
||||
status = 502
|
||||
}
|
||||
}
|
||||
host-connection-pool {
|
||||
max-open-requests = 1024
|
||||
idle-timeout = 60s
|
||||
}
|
||||
}
|
||||
management.health-checks.readiness-checks {
|
||||
gateway-http-readiness = "org.eclipse.ditto.gateway.service.health.GatewayHttpReadinessCheck"
|
||||
}
|
||||
management.health-checks.liveness-checks {
|
||||
subsystem-health = "org.eclipse.ditto.internal.utils.health.SubsystemHealthCheck"
|
||||
}
|
||||
}
|
||||
|
||||
authentication-dispatcher {
|
||||
type = Dispatcher
|
||||
executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
|
||||
thread-pool-executor {
|
||||
core-pool-size-min = 4
|
||||
core-pool-size-factor = 2.0
|
||||
core-pool-size-max = 8
|
||||
}
|
||||
throughput = 100
|
||||
}
|
||||
|
||||
signal-enrichment-cache-dispatcher {
|
||||
type = Dispatcher
|
||||
executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
|
||||
}
|
||||
169
configuration/ditto/modify-jar.py
Normal file
169
configuration/ditto/modify-jar.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Modify the reference.conf inside the Ditto gateway JAR.
|
||||
Inserts ditto { gateway { authentication { pre-authentication { enabled = true } } } }
|
||||
at the root level, before the final closing brace.
|
||||
"""
|
||||
import zipfile
|
||||
import shutil
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
JAR_PATH = "/tmp/ditto-jar-mod/ditto-gateway-service-3.8.12-allinone.jar"
|
||||
AUTH_BLOCK = """
|
||||
### Custom Ditto auth override - pre-authentication enabled
|
||||
ditto {
|
||||
gateway {
|
||||
authentication {
|
||||
pre-authentication {
|
||||
enabled = true
|
||||
}
|
||||
devops {
|
||||
secured = false
|
||||
devops-authentication-method = "basic"
|
||||
password = "ditto-devops-secret"
|
||||
status-secured = false
|
||||
status-authentication-method = "basic"
|
||||
statusPassword = "ditto-status-secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def main():
|
||||
os.makedirs("/tmp/ditto-jar-mod", exist_ok=True)
|
||||
|
||||
print("=== Step 1: Extracting JAR ===")
|
||||
result = subprocess.run(
|
||||
["docker", "run", "--rm", "eclipse/ditto-gateway:latest",
|
||||
"cat", "/opt/ditto/ditto-gateway-service-3.8.12-allinone.jar"],
|
||||
capture_output=True, check=True
|
||||
)
|
||||
with open(JAR_PATH, "wb") as f:
|
||||
f.write(result.stdout)
|
||||
print(f"JAR: {len(result.stdout)} bytes")
|
||||
|
||||
print("=== Step 2: Modifying reference.conf ===")
|
||||
with zipfile.ZipFile(JAR_PATH, 'r') as zin:
|
||||
ref_conf = zin.read("reference.conf").decode("utf-8")
|
||||
|
||||
lines = ref_conf.split('\n')
|
||||
total_lines = len(lines)
|
||||
print(f"reference.conf: {total_lines} lines")
|
||||
|
||||
# Find the LAST closing brace at root level (depth 0)
|
||||
# Track depth through the entire file
|
||||
depth = 0
|
||||
last_root_close_idx = None
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith('#'):
|
||||
continue
|
||||
|
||||
# Count braces (simple approach - count { and })
|
||||
# This isn't perfect for HOCON but works for our case
|
||||
for ch in stripped:
|
||||
if ch == '{':
|
||||
depth += 1
|
||||
elif ch == '}':
|
||||
depth -= 1
|
||||
|
||||
if depth == 0:
|
||||
last_root_close_idx = i
|
||||
|
||||
if last_root_close_idx is None:
|
||||
print("ERROR: Could not find root-level closing brace!")
|
||||
sys.exit(1)
|
||||
|
||||
insert_idx = last_root_close_idx
|
||||
print(f"Last root-level '}}' at line {insert_idx + 1}: '{lines[insert_idx].strip()}'")
|
||||
|
||||
# Check if we need a comma before our block
|
||||
# Look at the non-empty line before insert_idx
|
||||
prev_idx = insert_idx - 1
|
||||
while prev_idx >= 0 and lines[prev_idx].strip() == '':
|
||||
prev_idx -= 1
|
||||
|
||||
if prev_idx >= 0:
|
||||
prev_stripped = lines[prev_idx].strip()
|
||||
if prev_stripped.endswith('}'):
|
||||
# Need to add a comma
|
||||
lines[prev_idx] = lines[prev_idx].rstrip()
|
||||
if not lines[prev_idx].endswith(','):
|
||||
lines[prev_idx] += ','
|
||||
print(f"Added comma to line {prev_idx + 1}")
|
||||
|
||||
# Insert our block
|
||||
auth_lines = AUTH_BLOCK.split('\n')
|
||||
new_lines = lines[:insert_idx] + auth_lines + lines[insert_idx:]
|
||||
modified_conf = '\n'.join(new_lines)
|
||||
|
||||
print(f"Modified: {total_lines} -> {len(new_lines)} lines")
|
||||
|
||||
# Verify brace balance
|
||||
depth = 0
|
||||
for i, line in enumerate(new_lines):
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith('#'):
|
||||
continue
|
||||
for ch in stripped:
|
||||
if ch == '{':
|
||||
depth += 1
|
||||
elif ch == '}':
|
||||
depth -= 1
|
||||
if depth < 0:
|
||||
print(f"ERROR: Negative depth at line {i+1}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Brace depth check: {depth} (should be 0)")
|
||||
if depth != 0:
|
||||
print("ERROR: Unbalanced braces!")
|
||||
sys.exit(1)
|
||||
|
||||
# Verify ditto is at root level
|
||||
for i, line in enumerate(new_lines):
|
||||
if line.strip() == 'ditto {':
|
||||
indent = len(line) - len(line.lstrip())
|
||||
print(f"'ditto {{' at line {i+1}, indent: {indent}")
|
||||
if indent != 0:
|
||||
print(f"WARNING: Expected indent 0, got {indent}")
|
||||
break
|
||||
|
||||
print("=== Step 3: Creating unsigned JAR ===")
|
||||
skip_files = set()
|
||||
with zipfile.ZipFile(JAR_PATH, 'r') as zin:
|
||||
for name in zin.namelist():
|
||||
if name.startswith("META-INF/"):
|
||||
upper = name.upper()
|
||||
if upper.endswith(".SF") or upper.endswith(".RSA") or upper.endswith(".DSA") or upper == "MANIFEST.MF":
|
||||
skip_files.add(name)
|
||||
|
||||
with zipfile.ZipFile(JAR_PATH + ".new", 'w', zipfile.ZIP_DEFLATED) as zout:
|
||||
for item in zin.infolist():
|
||||
if item.filename in skip_files:
|
||||
continue
|
||||
data = zin.read(item.filename)
|
||||
if item.filename == "reference.conf":
|
||||
data = modified_conf.encode("utf-8")
|
||||
info = zipfile.ZipInfo(filename=item.filename, date_time=item.date_time)
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
info.external_attr = item.external_attr
|
||||
zout.writestr(info, data)
|
||||
|
||||
shutil.move(JAR_PATH + ".new", JAR_PATH)
|
||||
|
||||
print("=== Step 4: Verifying ===")
|
||||
with zipfile.ZipFile(JAR_PATH, 'r') as z:
|
||||
ref = z.read("reference.conf").decode("utf-8")
|
||||
assert "pre-authentication" in ref
|
||||
sig = [n for n in z.namelist() if n.startswith("META-INF/") and n.upper().endswith(('.SF', '.RSA', '.DSA'))]
|
||||
print(f"OK Signature files: {len(sig)}")
|
||||
print(f"OK JAR size: {os.path.getsize(JAR_PATH)}")
|
||||
|
||||
print("\n=== DONE ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
70
configuration/ditto/oauth2-server.js
Normal file
70
configuration/ditto/oauth2-server.js
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Minimal JWT/OAuth2 server for Ditto
|
||||
# Serves a JWKS endpoint and validates tokens signed with DITTO_JWT_SECRET
|
||||
|
||||
cat > /tmp/oauth2-server.js << 'EOF'
|
||||
const http = require('http');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const SECRET = process.env.DITTO_JWT_SECRET || 'my-ditto-jwt-secret-key-12345';
|
||||
const PORT = 3000;
|
||||
|
||||
function base64url(buf) {
|
||||
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
// Generate a token
|
||||
function generateToken(sub, scope) {
|
||||
const header = { alg: 'RS256', typ: 'JWT', kid: 'ditto-local' };
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const payload = {
|
||||
iss: 'http://localhost:' + PORT,
|
||||
sub: sub,
|
||||
aud: 'ditto:cognito',
|
||||
iat: now,
|
||||
exp: now + 3600,
|
||||
scope: scope
|
||||
};
|
||||
const h = base64url(Buffer.from(JSON.stringify(header)));
|
||||
const p = base64url(Buffer.from(JSON.stringify(payload)));
|
||||
const sig = base64url(
|
||||
crypto.createHmac('sha256', SECRET).update(h + '.' + p).digest()
|
||||
);
|
||||
return h + '.' + p + '.' + sig;
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.url === '/.well-known/openid-configuration') {
|
||||
res.end(JSON.stringify({
|
||||
issuer: 'http://localhost:' + PORT,
|
||||
jwks_uri: 'http://localhost:' + PORT + '/.well-known/jwks.json',
|
||||
token_endpoint: 'http://localhost:' + PORT + '/token'
|
||||
}));
|
||||
} else if (req.url === '/.well-known/jwks.json') {
|
||||
// Extract public key from secret (for HS256 we just return the secret as k)
|
||||
const jwk = {
|
||||
kty: 'oct',
|
||||
kid: 'ditto-local',
|
||||
use: 'sig',
|
||||
alg: 'HS256',
|
||||
k: base64url(Buffer.from(SECRET))
|
||||
};
|
||||
res.end(JSON.stringify({ keys: [jwk] }));
|
||||
} else if (req.url === '/token') {
|
||||
const token = generateToken('ditto', 'READ_WRITE');
|
||||
res.end(JSON.stringify({ access_token: token, token_type: 'Bearer', expires_in: 3600 }));
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end('{}');
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
console.log('OAuth2 server listening on port ' + PORT);
|
||||
console.log('Token: ' + generateToken('ditto', scope='READ_WRITE'));
|
||||
});
|
||||
EOF
|
||||
|
||||
node /tmp/oauth2-server.js
|
||||
14
configuration/ditto/override.conf
Normal file
14
configuration/ditto/override.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
ditto {
|
||||
gateway {
|
||||
authentication {
|
||||
pre-authentication {
|
||||
enabled = true
|
||||
}
|
||||
devops {
|
||||
secured = false
|
||||
devops-authentication-method = "basic"
|
||||
password = "ditto-devops-secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
ditto-things.yml
Normal file
25
ditto-things.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
ditto-things:
|
||||
image: eclipse/ditto-things:latest
|
||||
container_name: smart-city-ditto-things
|
||||
restart: unless-stopped
|
||||
hostname: ditto-things
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
- BIND_HOSTNAME=0.0.0.0
|
||||
- DITTO_JWT_SECRET=my-ditto-jwt-secret-key-12345
|
||||
- MONGO_HOST=smart-city-ditto-mongodb
|
||||
- MONGO_PORT=27017
|
||||
- MONGO_DB=Things
|
||||
- MONGODB_URI=mongodb://smart-city-ditto-mongodb:27017/Things
|
||||
- AKKA_REMOTE_ENABLED=false
|
||||
- JAVA_TOOL_OPTIONS=-Dditto.things.authentication.devops.password=ditto-devops-secret
|
||||
networks:
|
||||
traefik-public:
|
||||
aliases:
|
||||
- ditto-cluster
|
||||
- ditto-things
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
- "traefik.http.services.ditto-things.loadbalancer.server.port=8080"
|
||||
|
||||
ditto-gateway:
|
||||
image: eclipse/ditto-gateway:latest
|
||||
image: eclipse/ditto-gateway:custom
|
||||
container_name: smart-city-ditto-gateway
|
||||
restart: unless-stopped
|
||||
hostname: ditto-gateway
|
||||
@@ -85,19 +85,11 @@ services:
|
||||
- DITTO_GW_MQTT_BROKER=smart-city-mosquitto:1883
|
||||
- DITTO_GW_MQTT_TOPIC_FILTER=smartcity/#
|
||||
- DEVOPS_PASSWORD=ditto-devops-secret
|
||||
- ENABLE_PRE_AUTHENTICATION=true
|
||||
- JAVA_TOOL_OPTIONS=-Dditto.gateway.authentication.devops.password=ditto-devops-secret -Dditto.gateway.authentication.devops.secured=true -Dditto.gateway.authentication.devops.devops-authentication-method=basic
|
||||
networks:
|
||||
traefik-public:
|
||||
aliases:
|
||||
- ditto-cluster
|
||||
- ditto-gateway
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ditto-gateway.rule=Host(`ditto.digitribe.fr`)"
|
||||
- "traefik.http.routers.ditto-gateway.entrypoints=websecure"
|
||||
- "traefik.http.routers.ditto-gateway.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.ditto-gateway.loadbalancer.server.port=8080"
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
|
||||
48
flink/docker-compose.yml
Normal file
48
flink/docker-compose.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
# Smart City Digital Twin Martinique — Apache Flink
|
||||
# Usage: docker compose -f flink/docker-compose.yml up -d
|
||||
# Image officielle Apache Flink 1.20.1 avec digest vérifié
|
||||
|
||||
networks:
|
||||
smartcity-shared:
|
||||
external: true
|
||||
|
||||
services:
|
||||
jobmanager:
|
||||
image: apache/flink:1.20.1-scala_2.12-java17@sha256:ecc5785594eff2d94e29e6b116b3124c0cdb3a9c952ebdf38ef0fef90fb9913d
|
||||
container_name: flink-jobmanager
|
||||
command: jobmanager
|
||||
networks:
|
||||
- smartcity-shared
|
||||
ports:
|
||||
- "8081:8081" # Flink Web UI
|
||||
environment:
|
||||
- |
|
||||
FLINK_PROPERTIES=
|
||||
jobmanager.rpc.address: jobmanager
|
||||
jobmanager.memory.process.size: 1024m
|
||||
taskmanager.memory.process.size: 1024m
|
||||
taskmanager.numberOfTaskSlots: 4
|
||||
parallelism.default: 2
|
||||
rest.port: 8081
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
|
||||
taskmanager:
|
||||
image: apache/flink:1.20.1-scala_2.12-java17@sha256:ecc5785594eff2d94e29e6b116b3124c0cdb3a9c952ebdf38ef0fef90fb9913d
|
||||
container_name: flink-taskmanager
|
||||
command: taskmanager
|
||||
networks:
|
||||
- smartcity-shared
|
||||
depends_on:
|
||||
- jobmanager
|
||||
environment:
|
||||
- |
|
||||
FLINK_PROPERTIES=
|
||||
jobmanager.rpc.address: jobmanager
|
||||
taskmanager.memory.process.size: 1024m
|
||||
taskmanager.numberOfTaskSlots: 4
|
||||
parallelism.default: 2
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=false"
|
||||
@@ -3,7 +3,7 @@ FROM jupyterhub/jupyterhub:5.3.0
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git sudo && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip install --no-cache-dir \
|
||||
git+https://github.com/jupyterhub/nativeauthenticator.git@main \
|
||||
@@ -12,9 +12,17 @@ RUN pip install --no-cache-dir \
|
||||
jupyterlab \
|
||||
notebook
|
||||
|
||||
# Create eric user with password-less sudo
|
||||
RUN useradd -m -s /bin/bash -u 1000 eric && \
|
||||
echo "eric ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
|
||||
chown -R eric:eric /home/eric
|
||||
|
||||
# Create the directory for JupyterHub data
|
||||
RUN mkdir -p /srv/jupyterhub && chown -R 1000:1000 /srv/jupyterhub
|
||||
|
||||
ARG BUILD_DATE=unknown
|
||||
RUN echo "Build date: ${BUILD_DATE}" > /tmp/build-info.txt
|
||||
|
||||
COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
|
||||
|
||||
WORKDIR /srv/jupyterhub
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# JupyterHub configuration for Smart City VRE
|
||||
# Uses NativeAuthenticator with LocalProcessSpawner
|
||||
# Build: 2026-06-01-0915
|
||||
|
||||
import sys as _sys
|
||||
|
||||
c.JupyterHub.ip = '0.0.0.0'
|
||||
c.JupyterHub.port = 8000
|
||||
@@ -9,14 +13,14 @@ c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'
|
||||
c.Authenticator.admin_users = {'admin'}
|
||||
c.Authenticator.allow_all = True
|
||||
|
||||
# Spawner — use SimpleLocalProcessSpawner (default in JupyterHub 5.x)
|
||||
# This spawner runs singleuser servers as subprocesses
|
||||
c.JupyterHub.spawner_class = 'jupyterhub.spawner.SimpleLocalProcessSpawner'
|
||||
# Spawner: Simple local spawner (single-node, shared environment)
|
||||
c.JupyterHub.spawner_class = 'simple'
|
||||
c.Spawner.cmd = ['jupyterhub-singleuser']
|
||||
c.Spawner.default_url = '/lab'
|
||||
c.Spawner.http_timeout = 300
|
||||
c.Spawner.start_timeout = 300
|
||||
c.Spawner.http_timeout = 120
|
||||
|
||||
# Database and cookies - use absolute paths
|
||||
# Database and cookies - use absolute path (4 slashes!)
|
||||
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
|
||||
c.JupyterHub.db_url = 'sqlite:////srv/jupyterhub/jupyterhub.sqlite'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user