Skip to content

Commit 7e1b0ed

Browse files
authored
Merge pull request #80 from thin-edge/feat-support-basic-auth
feat: add support Cumulocity basic auth
2 parents b8b8073 + e0ceb1e commit 7e1b0ed

File tree

10 files changed

+341
-27
lines changed

10 files changed

+341
-27
lines changed

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ RUN wget -O - https://thin-edge.io/install-services.sh | sh -s -- s6_overlay \
5959
# TODO: Can thin-edge.io set permissions during installation?
6060
RUN usermod -u "$USERID" tedge \
6161
&& groupmod -g "$GROUPID" tedge \
62+
# create empty folder so basic auth credentials can be mounted to it
63+
&& mkdir -p /etc/tedge/credentials \
6264
&& chown -R tedge:tedge /etc/tedge \
6365
&& chown -R tedge:tedge /var/tedge \
6466
&& echo "tedge ALL = (ALL) NOPASSWD:SETENV: /usr/bin/tedge, /etc/tedge/sm-plugins/[a-zA-Z0-9]*, /bin/sync, /sbin/init, /usr/bin/tedgectl, /bin/kill, /usr/bin/tedge-container, /usr/bin/docker, /usr/bin/podman, /usr/bin/podman-remote, /usr/bin/podman-compose" >/etc/sudoers.d/tedge \
@@ -90,6 +92,8 @@ COPY files/tedge/log_upload.toml /etc/tedge/operations/
9092
COPY files/tedge/log_upload_container.toml /etc/tedge/operations/
9193
# Script to fix the permissions / ownership which is called on container startup
9294
COPY files/tedge/fix-permissions.sh /usr/bin/
95+
# Helper script to set the Cumulocity Basic Auth more easily
96+
COPY files/tedge/set-c8y-basic-auth.sh /usr/bin/
9397

9498

9599
ENV S6_BEHAVIOUR_IF_STAGE2_FAILS=2

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ The following are required in order to deploy the container
3333

3434
[Option 2b (Cumulocity Certificate Authority Preview): Container network (Recommended)](./docs/CONTAINER_OPTION2_with_ca.md)
3535

36+
[Option 2c (Basic Auth): Container network (Recommended)](./docs/CONTAINER_OPTION2_with_basic_auth.md)
37+
3638

3739
### Settings
3840

cont-init.d/50_configure.sh

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,55 @@ load_from_file() {
8686
echo "Loading device certificates from file (no-op)"
8787
}
8888

89+
get_auth_mode() {
90+
value=$(tedge config get c8y.auth_method ||:)
91+
output=certificate
92+
case "$value" in
93+
auto)
94+
if [ -f "$(tedge config get c8y.credentials_path)" ]; then
95+
output=basic
96+
fi
97+
;;
98+
basic)
99+
output=basic
100+
;;
101+
certificate)
102+
output=certificate
103+
;;
104+
esac
105+
echo "$output"
106+
}
107+
108+
load_c8y_basic_auth_from_env() {
109+
if [ -z "$C8Y_DEVICE_USER" ] && [ -z "$C8Y_DEVICE_PASSWORD" ]; then
110+
return 1
111+
fi
112+
113+
echo "Loading device basic auth credentials from environment variables" >&2
114+
set-c8y-basic-auth.sh "$C8Y_DEVICE_USER" "$C8Y_DEVICE_PASSWORD"
115+
}
116+
117+
load_c8y_basic_auth_from_secrets() {
118+
if [ ! -f /run/secrets/credentials ]; then
119+
return 1
120+
fi
121+
122+
echo "Loading device basic auth credentials from docker secrets" >&2
123+
CREDENTIALS_PATH=$(tedge config get c8y.credentials_path)
124+
cat /run/secrets/credentials > "$CREDENTIALS_PATH"
125+
chmod 600 "$CREDENTIALS_PATH"
126+
}
127+
128+
load_c8y_basic_auth_from_file() {
129+
CREDENTIALS_PATH=$(tedge config get c8y.credentials_path)
130+
if [ ! -f "$CREDENTIALS_PATH" ]; then
131+
return 1
132+
fi
133+
134+
# Don't actually do anything, but confirm the presence of device credentials
135+
echo "Loading device basic auth credentials from file (no-op)"
136+
}
137+
89138
create_tedge_config_symlink() {
90139
# Store the tedge.toml under the data dir, and
91140
# use a symlink from /etc/tedge/tedge.toml to point to the data dir.
@@ -138,19 +187,24 @@ fi
138187
AGENT_STATE=$(tedge config get agent.state.path)
139188
mkdir -p "$AGENT_STATE"
140189

141-
#
142-
# Try loading the device certificates from several locations, taking the first successful function
190+
# Try loading device and basic auth from multiple locations taking the first successful function
143191
# Don't fail as users are allowed to start up a container without a device certificate (e.g. when only running the tedge-agent)
144-
#
145192
load_from_env || load_from_secrets || load_from_file ||:
146193

194+
load_c8y_basic_auth_from_env || load_c8y_basic_auth_from_secrets || load_c8y_basic_auth_from_file ||:
147195

148196
# Support variable set by go-c8y-cli
149197
if [ -n "$C8Y_DOMAIN" ] && [ -z "${TEDGE_C8Y_URL:-}" ]; then
150198
echo "Setting c8y.url from C8Y_DOMAIN env variable. $C8Y_DOMAIN" >&2
151199
tedge config set c8y.url "$C8Y_DOMAIN"
152200
fi
153201

202+
# set device.id for basic auth case which requires it, though when
203+
# using a device certificate, the device.id will take precedence
204+
if [ -n "$DEVICE_ID" ]; then
205+
tedge config set device.id "$DEVICE_ID"
206+
fi
207+
154208
random_sleep() {
155209
VALUE=$(awk "BEGIN { srand(); print int(rand()*32768 % $MAX_RANDOM_WAIT) }")
156210
echo "Sleeping ${VALUE}s" >&2
@@ -190,6 +244,9 @@ https://${TARGET_URL}/apps/devicemanagement/index.html#/deviceregistration?exter
190244
EOT
191245
}
192246

247+
# Resolve which auth mode is being used
248+
AUTH_METHOD=$(get_auth_mode)
249+
193250
#
194251
# Connect the mappers (if they are configured and not already connected)
195252
#
@@ -199,26 +256,30 @@ for MAPPER in $MAPPERS; do
199256

200257
case "$MAPPER" in
201258
c8y)
202-
if [ "$CA" = "c8y" ]; then
203-
# Register device using the Cumulocity certificate-authority feature
204-
PUBLIC_CERT=$(tedge config get device.cert_path 2>/dev/null ||:)
205-
if [ ! -f "$PUBLIC_CERT" ]; then
206-
if [ -z "$DEVICE_ONE_TIME_PASSWORD" ]; then
207-
DEVICE_ONE_TIME_PASSWORD=$(get_random_code)
208-
SHOW_REGISTRATION_BANNER=1
209-
fi
210-
211-
if [ "$SHOW_REGISTRATION_BANNER" = 1 ]; then
212-
show_registration_banner
213-
fi
214-
215-
# Download certificate
216-
echo "Downloading certificate. device-id=$DEVICE_ID" >&2
217-
if ! tedge cert download c8y --device-id "$DEVICE_ID" --one-time-password "$DEVICE_ONE_TIME_PASSWORD" --retry-every 5s --max-timeout 300s; then
218-
echo "WARNING: Failed to download certificate" >&2
259+
case "$AUTH_METHOD" in
260+
certificate)
261+
if [ "$CA" = "c8y" ]; then
262+
# Register device using the Cumulocity certificate-authority feature
263+
PUBLIC_CERT=$(tedge config get device.cert_path 2>/dev/null ||:)
264+
if [ ! -f "$PUBLIC_CERT" ]; then
265+
if [ -z "$DEVICE_ONE_TIME_PASSWORD" ]; then
266+
DEVICE_ONE_TIME_PASSWORD=$(get_random_code)
267+
SHOW_REGISTRATION_BANNER=1
268+
fi
269+
270+
if [ "$SHOW_REGISTRATION_BANNER" = 1 ]; then
271+
show_registration_banner
272+
fi
273+
274+
# Download certificate
275+
echo "Downloading certificate. device-id=$DEVICE_ID" >&2
276+
if ! tedge cert download c8y --device-id "$DEVICE_ID" --one-time-password "$DEVICE_ONE_TIME_PASSWORD" --retry-every 5s --max-timeout 300s; then
277+
echo "WARNING: Failed to download certificate" >&2
278+
fi
279+
fi
219280
fi
220-
fi
221-
fi
281+
;;
282+
esac
222283
;;
223284
esac
224285

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
## Option 2: Container network and using Cumulocity Basic Auth
2+
3+
**When to use it?**
4+
5+
* Familiar with containers
6+
* Basic understanding of container networking
7+
* You can't use certificate based auth for reason
8+
9+
## Getting Started
10+
11+
12+
### Option 1: Using docker run
13+
14+
1. Pull the latest image
15+
16+
```sh
17+
docker pull ghcr.io/thin-edge/tedge-container-bundle
18+
```
19+
20+
1. Create Cumulocity basic auth credentials for your new device. For convenience, you can use go-c8y-cli to do this
21+
22+
```sh
23+
c8y deviceregistration register-basic --id tedge_abcdef
24+
```
25+
26+
1. Set some environment variable based on the Cumulocity instance you wish to connect to, and the device id
27+
28+
```sh
29+
export TEDGE_C8Y_URL="example-demo.eu-latest.cumulocity.com"
30+
export DEVICE_ID="tedge_abcdef"
31+
export C8Y_DEVICE_USER="t12345/device_${DEVICE_ID}"
32+
export C8Y_DEVICE_PASSWORD="<<code_max_32_chars>>"
33+
```
34+
35+
**Note** The username must be in the form of `{tenant}/device_{name}`.
36+
37+
1. Create a docker volume which will be used to store the device credentials, and a volume for the tedge and mosquitto data
38+
39+
```sh
40+
docker network create tedge
41+
docker volume create device-creds
42+
docker volume create tedge
43+
```
44+
45+
3. Create a new device credentials
46+
47+
```sh
48+
docker run --rm -it \
49+
-v "device-creds:/etc/tedge/credentials" \
50+
-e "TEDGE_C8Y_CREDENTIALS_PATH=/etc/tedge/credentials/credentials.toml" \
51+
ghcr.io/thin-edge/tedge-container-bundle:latest \
52+
/usr/bin/set-c8y-basic-auth.sh "$C8Y_DEVICE_USER" "$C8Y_DEVICE_PASSWORD"
53+
```
54+
55+
Alternatively, you can set the Cumulocity device username and password using environment variables, however be aware that they could then be read by anyone with access to the container engine.
56+
57+
```sh
58+
-e "C8Y_DEVICE_USER=$C8Y_DEVICE_USER" \
59+
-e "C8Y_DEVICE_PASSWORD=$C8Y_DEVICE_PASSWORD" \
60+
```
61+
62+
1. Start the container
63+
64+
```sh
65+
docker run -d \
66+
--name tedge \
67+
--restart always \
68+
--add-host host.docker.internal:host-gateway \
69+
--network tedge \
70+
-p "127.0.0.1:1883:1883" \
71+
-p "127.0.0.1:8000:8000" \
72+
-p "127.0.0.1:8001:8001" \
73+
-v device-creds:/etc/tedge/credentials \
74+
-v tedge:/data/tedge \
75+
-v /var/run/docker.sock:/var/run/docker.sock:rw \
76+
-e TEDGE_C8Y_OPERATIONS_AUTO_LOG_UPLOAD=always \
77+
-e "DEVICE_ID=${DEVICE_ID}" \
78+
-e "TEDGE_C8Y_URL=${TEDGE_C8Y_URL}" \
79+
-e "TEDGE_C8Y_AUTH_METHOD=auto" \
80+
-e "TEDGE_C8Y_CREDENTIALS_PATH=/etc/tedge/credentials/credentials.toml" \
81+
ghcr.io/thin-edge/tedge-container-bundle:latest
82+
```
83+
84+
With this option, you can change the host port mapping in case it conflicts with any other services running on the host, e.g. other services which are already using the ports that thin-edge.io wants to use.
85+
86+
```sh
87+
docker run -d \
88+
--name tedge \
89+
--restart always \
90+
--add-host host.docker.internal:host-gateway \
91+
--network tedge \
92+
-p "127.0.0.1:1884:1883" \
93+
-p "127.0.0.1:9000:8000" \
94+
-p "127.0.0.1:9001:8001" \
95+
-v device-creds:/etc/tedge/credentials \
96+
-v tedge:/data/tedge \
97+
-v /var/run/docker.sock:/var/run/docker.sock:rw \
98+
-e TEDGE_C8Y_OPERATIONS_AUTO_LOG_UPLOAD=always \
99+
-e "DEVICE_ID=${DEVICE_ID}" \
100+
-e "TEDGE_C8Y_URL=${TEDGE_C8Y_URL}" \
101+
-e "TEDGE_C8Y_AUTH_METHOD=auto" \
102+
-e "TEDGE_C8Y_CREDENTIALS_PATH=/etc/tedge/credentials/credentials.toml" \
103+
ghcr.io/thin-edge/tedge-container-bundle:latest
104+
```
105+
106+
107+
### Option 2: Using docker compose
108+
109+
Note: This docker compose example uses environment variable to set the basic auth. Ideally you would not set the credentials this ways as it makes them readable to anyone whom has access to the container engine.
110+
111+
1. In a shell, create a new folder and change directory into it. The name of the folder will be your docker compose project name
112+
113+
1. Create a `.env` file with the following contents
114+
115+
```sh
116+
TEDGE_C8Y_URL="example-demo.eu-latest.cumulocity.com"
117+
export DEVICE_ID="tedge_abcdef"
118+
export C8Y_DEVICE_USER="t12345/device_${DEVICE_ID}"
119+
export C8Y_DEVICE_PASSWORD="<<code_max_32_chars>>"
120+
121+
# any other custom thin-edge.io configuration that you want
122+
TEDGE_C8Y_OPERATIONS_AUTO_LOG_UPLOAD=always
123+
```
124+
125+
1. Create a `docker-compose.yaml` file with the following contents
126+
127+
```yaml
128+
services:
129+
tedge:
130+
image: ghcr.io/thin-edge/tedge-container-bundle
131+
restart: always
132+
environment:
133+
- TEDGE_C8Y_AUTH_METHOD=auto
134+
- TEDGE_C8Y_CREDENTIALS_PATH=/etc/tedge/credentials/credentials.toml
135+
env_file:
136+
- .env
137+
ports:
138+
- 127.0.0.1:1883:1883
139+
- 127.0.0.1:8000:8000
140+
- 127.0.0.1:8001:8001
141+
# When using docker, add access to the host
142+
# if you want to be able to ssh into the host from the container
143+
extra_hosts:
144+
- host.docker.internal:host-gateway
145+
tmpfs:
146+
- /tmp
147+
volumes:
148+
- device-creds:/etc/tedge/credentials
149+
- tedge:/data/tedge
150+
# Enable docker from docker
151+
- /var/run/docker.sock:/var/run/docker.sock:rw
152+
153+
volumes:
154+
device-creds:
155+
tedge:
156+
```
157+
158+
1. Start the container using docker compose
159+
160+
```sh
161+
docker compose up -d
162+
```
163+
164+
## Using the tedge-container-bundle
165+
166+
### Subscribing to the MQTT broker
167+
168+
Assuming the container network is called `tedge`, then you can subscribe to the MQTT broker using the following command:
169+
170+
```sh
171+
docker run --rm -it \
172+
--network tedge \
173+
-e TEDGE_MQTT_CLIENT_HOST=tedge \
174+
ghcr.io/thin-edge/tedge-container-bundle \
175+
tedge mqtt sub '#'
176+
```
177+
178+
Or you can access the MQTT broker directly from the host using the port mappings:
179+
180+
```sh
181+
mosquitto_sub -h 127.0.0.1 -p 1883 -t '#'
182+
183+
# or if you used another port
184+
mosquitto_sub -h 127.0.0.1 -p 1884 -t '#'
185+
```

files/tedge/set-c8y-basic-auth.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/sh
2+
set -e
3+
if [ $# -lt 2 ]; then
4+
echo "ERROR: Expected 2 arguments." >&2
5+
echo "USAGE: $0 <C8Y_DEVICE_USER> <C8Y_DEVICE_PASSWORD>" >&2
6+
exit 1
7+
fi
8+
CREDENTIALS_PATH=$(tedge config get c8y.credentials_path)
9+
printf '[c8y]\nusername = "%s"\npassword = "%s"\n' "$1" "$2" > "$CREDENTIALS_PATH"
10+
chmod 600 "$CREDENTIALS_PATH"

0 commit comments

Comments
 (0)