diff --git a/.gitignore b/.gitignore index d0ada61..f7b3492 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,4 @@ *.out # Dependency directories (remove the comment below to include it) -# vendor/ - -#run-script including environment-variable values for test instances -run.ps1 \ No newline at end of file +# vendor/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4bcf4b7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "weather-api", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "env": { + "MONGO_HOST":"localhost:27017", + "MONGO_DB":"weathersensors", + "MONGO_COLLECTION":"data", + "MONGO_USER":"root", + "MONGO_PASSWORD":"rootPassXXX", + + "INFLUX_HOST":"localhost", + "INFLUX_TOKEN":"token", + "INFLUX_ORG":"weather-org", + "INFLUX_BUCKET":"weatherdata", + + "MQTT_HOST":"localhost:1883", + "MQTT_TOPIC":"sensor/#", + "MQTT_USER":"mqtt", + "MQTT_PASSWORD":"mqtt", + "MQTT_PUBLISH_DELAY":"1000", + "MQTT_ANONYMOUS":"false", + + "ACCESS_CONTROL_ALLOW_ORIGIN_HEADER":"*", + "USE_JWT_TOKEN_VALIDATION_URL":"false", + "JWT_TOKEN_VALIDATION_URL":"localhost:5000", + "USE_JWT_TOKEN_VALIDATION_SECRET":"false", + "JWT_TOKEN_VALIDATION_SECRET":"token_Secret_value", + + "ALLOW_UNREGISTERED_SENSORS":"true", + }, + } + ] +} diff --git a/README.md b/README.md index 4960631..c0217f9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Key | Default-Wert | Auswirkung MONGO_HOST | localhost:27017 | Hostadresse mongodb MONGO_DB | weathersensors | DB-Namen mongodb MONGO_USER | admin | Username mongodb -MONGO_PASS | admin | Passwort mongodb +MONGO_PASSWORD | admin | Passwort mongodb MONGO_COLLECTION | sensors | mongodb-Collection, in der Wettersensoren gespeichert werden INFLUX_HOST | localhost:8086 | Hostadresse influxdb INFLUX_TOKEN | token | Token für influxDB @@ -33,13 +33,13 @@ INFLUX_BUCKET | bucket_name | Bucket-Namen, in dem die Wetterdaten abgespeichert MQTT_HOST | localhost:1883 | Hostadresse MQTT-Broker MQTT_TOPIC | sensor/# | MQTT-Topic, in welchem nach Wetterdaten geschaut wird MQTT_USER | mqtt | Username für MQTT -MQTT_PASS | mqtt | Passwort für MQTT +MQTT_PASSWORD | mqtt | Passwort für MQTT MQTT_PUBLISH_DELAY | 1000 | Innerhalb dieser Zeitspanne wird ein Wetterdatensatz noch durch weiter eintreffende Werte ergänzt. Danach wird der Datensatz veröffentlicht (in Millisekunden) MQTT_ANONYMOUS | false | Anonyme Anmeldung am MQTT-Broker verwenden (ohne Username und Passwort) +ACCESS_CONTROL_ALLOW_ORIGIN_HEADER | * | CORS-Header +USE_JWT_TOKEN_VALIDATION_URL | false | Tokenvalidierung an einer URL +JWT_TOKEN_VALIDATION_URL | localhost:5000 | URL für die JWT-Token Validierung +USE_JWT_TOKEN_VALIDATION_SECRET | true | Tokenvalidierung mit der Angabe eines Secrets +JWT_TOKEN_VALIDATION_SECRET | token_Secret_value | Secret um die Signatur des JWT-Tokens zu überprüfen ALLOW_UNREGISTERED_SENSORS | false | Wetterdaten nicht registrierter Sensoren erlauben - -## Applikation lokal ausführen - -Eine lokal ausgeführte Test-Instanz der Wetter-API muss mit URLs, Tokens und ähnlichem über Umgebungsvariablen konfiguriert werden. -Das PowerShell-Skript `run_default.ps1` ist eine Vorlage für den start einer eigenen Instanz, lediglich die Umgebungsvariablen müsssen hierzu angepasst werden. Am besten wird der Inhalt dieses Skriptes in ein weiteres Skript (z.B. `run.ps1`) kopiert. Dieses wird von Git ignoriert, geheime Zugangsdaten (z.B. zu MQTT Broker, InfluxDB) werden so nicht ins Git-Repository eingefügt. diff --git a/api/rest-api.go b/api/rest-api.go index 2112116..41eb886 100644 --- a/api/rest-api.go +++ b/api/rest-api.go @@ -24,10 +24,19 @@ var bearerTokenRegexPattern = "^(?i:Bearer\\s+)([A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=] var bearerTokenRegex *regexp.Regexp = regexp.MustCompile(bearerTokenRegexPattern) -type UserClaims struct { +type User struct { Uid string `json:"uid"` Username string `json:"username"` Roles []string `json:"role"` +} + +type ValidationResponse struct { + ValidationSuccessfull bool + Identity User +} + +type UserClaims struct { + User jwt.StandardClaims } @@ -74,7 +83,8 @@ func (api *weatherRestApi) handleRequests() *mux.Router { //sensor specific stuff sensorRouter := router.PathPrefix("/{_dummy:(?i)sensor}").Subrouter() - sensorRouter.Use(api.IsAuthorized) + sensorRouter.Use(api.UseJwtTokenValidationSecret) + sensorRouter.Use(api.UseJwtTokenValidationUrl) sensorRouter.HandleFunc("/{id}/{_dummy:(?i)weather-data}", api.getWeatherDataHandler).Methods("GET") sensorRouter.HandleFunc("/{id}/{_dummy:(?i)weather-data}", api.addWeatherDataHandler).Methods("POST") @@ -281,14 +291,13 @@ func (api *weatherRestApi) homePageHandler(w http.ResponseWriter, r *http.Reques fmt.Fprintf(w, "Welcome to the Weather API!") } -func (api *weatherRestApi) IsAuthorized(next http.Handler) http.Handler { +func (api *weatherRestApi) UseJwtTokenValidationUrl(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !api.config.UseTokenAuthorization { + if !api.config.UseJwtTokenValidationUrl { next.ServeHTTP(w, r) return } - - req, err := http.NewRequest(http.MethodGet, api.config.ValidateTokenUrl, &bytes.Buffer{}) + req, err := http.NewRequest(http.MethodGet, api.config.JwtTokenValidationUrl, &bytes.Buffer{}) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -302,18 +311,35 @@ func (api *weatherRestApi) IsAuthorized(next http.Handler) http.Handler { return } - claims, err := api.parseToken(r.Header) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - r.Header.Set(userIdHeader, claims.Uid) - if resp.StatusCode == http.StatusOK { - next.ServeHTTP(w, r) + if resp.StatusCode != http.StatusOK { + http.Error(w, resp.Status, resp.StatusCode) return } - http.Error(w, "", http.StatusUnauthorized) + validation := new(ValidationResponse) + err = json.NewDecoder(resp.Body).Decode(validation) + if err != nil || !validation.ValidationSuccessfull { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + r.Header.Set(userIdHeader, validation.Identity.Uid) + next.ServeHTTP(w, r) + }) +} + +func (api *weatherRestApi) UseJwtTokenValidationSecret(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !api.config.UseJwtTokenValidationSecret { + next.ServeHTTP(w, r) + return + } + claims, err := api.parseToken(r.Header) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + r.Header.Set(userIdHeader, claims.Uid) + next.ServeHTTP(w, r) }) } @@ -331,7 +357,7 @@ func (api *weatherRestApi) parseToken(header http.Header) (*UserClaims, error) { jwtFromHeader, claims, func(token *jwt.Token) (interface{}, error) { - return []byte(api.config.JwtTokenSecret), nil + return []byte(api.config.JwtTokenValidationSecret), nil }, ) return claims, err diff --git a/config/config.go b/config/config.go index a7744e6..888fbcb 100644 --- a/config/config.go +++ b/config/config.go @@ -32,16 +32,18 @@ type MqttConfig struct { type RestConfig struct { AccessControlAllowOriginHeader string - UseTokenAuthorization bool - ValidateTokenUrl string - JwtTokenSecret string + Insecure bool + UseJwtTokenValidationUrl bool + JwtTokenValidationUrl string + UseJwtTokenValidationSecret bool + JwtTokenValidationSecret string } var MongoConfiguration = MongoConfig{ Host: getEnv("MONGO_HOST", "localhost:27017"), Database: getEnv("MONGO_DB", "weathersensors"), Username: getEnv("MONGO_USER", "admin"), - Password: getEnv("MONGO_PASS", "admin"), + Password: getEnv("MONGO_PASSWORD", "admin"), Collection: getEnv("MONGO_COLLECTION", "sensors"), } @@ -56,16 +58,17 @@ var MqttConfiguration = MqttConfig{ Host: getEnv("MQTT_HOST", "localhost:1883"), Topic: getEnv("MQTT_TOPIC", "sensor/#"), Username: getEnv("MQTT_USER", "mqtt"), - Password: getEnv("MQTT_PASS", "mqtt"), + Password: getEnv("MQTT_PASSWORD", "mqtt"), PublishDelay: getEnvDuration("MQTT_PUBLISH_DELAY", time.Second), AllowAnonymousAuthentication: getEnvBool("MQTT_ANONYMOUS", false), } var RestConfiguration = RestConfig{ AccessControlAllowOriginHeader: getEnv("ACCESS_CONTROL_ALLOW_ORIGIN_HEADER", "*"), - UseTokenAuthorization: getEnvBool("USE_TOKEN_AUTHORIZATION", false), - ValidateTokenUrl: getEnv("JWT_TOKEN_VALIDATION_URL", "https://api.swablab.de/ldap/validateToken"), - JwtTokenSecret: getEnv("JWT_TOKEN_SECRET", "my_token_string"), + UseJwtTokenValidationUrl: getEnvBool("USE_JWT_TOKEN_VALIDATION_URL", false), + JwtTokenValidationUrl: getEnv("JWT_TOKEN_VALIDATION_URL", "localhost:5000"), + UseJwtTokenValidationSecret: getEnvBool("USE_JWT_TOKEN_VALIDATION_SECRET", true), + JwtTokenValidationSecret: getEnv("JWT_TOKEN_VALIDATION_SECRET", "my_token_string"), } var AllowUnregisteredSensors = getEnvBool("ALLOW_UNREGISTERED_SENSORS", false) @@ -90,16 +93,6 @@ func getEnvBool(key string, fallback bool) bool { return fallback } -func getEnvInt(key string, fallback int64) int64 { - if value, ok := os.LookupEnv(key); ok { - if iValue, err := strconv.ParseInt(value, 10, 64); err == nil { - return iValue - } - } - - return fallback -} - func getEnvDuration(key string, fallback time.Duration) time.Duration { if value, ok := os.LookupEnv(key); ok { if iValue, err := strconv.ParseInt(value, 10, 64); err == nil { diff --git a/run_default.ps1 b/run_default.ps1 deleted file mode 100644 index d734f7c..0000000 --- a/run_default.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#build the application -go build main.go - -#set environment variables for weather-api configuration -Set-Item -Path "Env:INFLUX_HOST" -Value "localhost:8086" -Set-Item -Path "Env:INFLUX_TOKEN" -Value "token" -Set-Item -Path "Env:INFLUX_ORG" -Value "org-name" -Set-Item -Path "Env:INFLUX_BUCKET" -Value "bucket-name" - -Set-Item -Path "Env:MQTT_HOST" -Value "localhost:1883" -Set-Item -Path "Env:MQTT_TOPIC" -Value "sensor/#" -Set-Item -Path "Env:MQTT_USER" -Value "mqtt" -Set-Item -Path "Env:MQTT_PASS" -Value "mqtt" -Set-Item -Path "Env:MQTT_PUBLISH_DELAY" -Value "1000" -Set-Item -Path "Env:MQTT_ANONYMOUS" -Value "false" - -Set-Item -Path "Env:MONGO_HOST" -Value "localhost:27017" -Set-Item -Path "Env:MONGO_DB" -Value "weathersensors" -Set-Item -Path "Env:MONGO_COLLECTION" -Value "sensors" -Set-Item -Path "Env:MONGO_USER" -Value "admin" -Set-Item -Path "Env:MONGO_PASS" -Value "admin" - -Set-Item -Path "Env:ALLOW_UNREGISTERED_SENSORS" -Value "false" - -#start application -Start-Process "main.exe" -Wait -NoNewWindow