1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-24 06:28:42 +01:00

feat!: removes legacy authentication model (#2633)

This commit is contained in:
Amir Raminfar
2023-12-31 14:05:41 -08:00
committed by GitHub
parent cb6adf275d
commit 6eb356aaa9
14 changed files with 9 additions and 338 deletions

View File

@@ -21,9 +21,6 @@
> >
<mdi:cog /> <mdi:cog />
</router-link> </router-link>
<a :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured" class="btn btn-circle btn-sm">
<mdi:logout />
</a>
</div> </div>
<a <a
class="input input-sm mt-4 inline-flex cursor-pointer items-center gap-2 font-light hover:border-primary" class="input input-sm mt-4 inline-flex cursor-pointer items-center gap-2 font-light hover:border-primary"
@@ -40,5 +37,5 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const { base, secured, hostname } = config; const { hostname } = config;
</script> </script>

View File

@@ -32,9 +32,6 @@
<router-link :to="{ name: 'settings' }" class="btn btn-outline btn-sm"> <router-link :to="{ name: 'settings' }" class="btn btn-outline btn-sm">
<mdi:cog /> {{ $t("button.settings") }} <mdi:cog /> {{ $t("button.settings") }}
</router-link> </router-link>
<a class="btn btn-outline btn-sm" :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured">
<mdi:logout /> {{ $t("button.logout") }}
</a>
</div> </div>
<ul class="menu"> <ul class="menu">
@@ -56,7 +53,6 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const { base, secured } = config;
import { sessionHost } from "@/composable/storage"; import { sessionHost } from "@/composable/storage";
const store = useContainerStore(); const store = useContainerStore();
const route = useRoute(); const route = useRoute();

View File

@@ -1,5 +1,5 @@
<template> <template>
<div v-if="!authorizationNeeded"> <div>
<mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu> <mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu>
<splitpanes @resized="onResized($event)"> <splitpanes @resized="onResized($event)">
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav"> <pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
@@ -71,7 +71,6 @@
// @ts-ignore - splitpanes types are not available // @ts-ignore - splitpanes types are not available
import { Splitpanes, Pane } from "splitpanes"; import { Splitpanes, Pane } from "splitpanes";
import { collapseNav } from "@/stores/settings"; import { collapseNav } from "@/stores/settings";
const { authorizationNeeded } = config;
const containerStore = useContainerStore(); const containerStore = useContainerStore();
const { activeContainers } = storeToRefs(containerStore); const { activeContainers } = storeToRefs(containerStore);

View File

@@ -32,7 +32,6 @@
import { Container } from "@/models/Container"; import { Container } from "@/models/Container";
const { t } = useI18n(); const { t } = useI18n();
const { showToast } = useToast();
const { version } = config; const { version } = config;
const containerStore = useContainerStore(); const containerStore = useContainerStore();
const { containers, ready } = storeToRefs(containerStore) as unknown as { const { containers, ready } = storeToRefs(containerStore) as unknown as {
@@ -66,14 +65,6 @@ watchEffect(() => {
setTitle(t("title.dashboard", { count: runningContainers.length })); setTitle(t("title.dashboard", { count: runningContainers.length }));
} }
}); });
if (config.secured) {
showToast({
title: "Deprecation Warning",
message: `The secured option is deprecated and will be removed in a future versions. See <a href="https://github.com/amir20/dozzle/issues/2630" target="_blank">this issue</a> for more information.`,
type: "warning",
});
}
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>
:deep(tr td) { :deep(tr td) {

View File

@@ -5,8 +5,6 @@ const text = document.querySelector("script#config__json")?.textContent || "{}";
export interface Config { export interface Config {
version: string; version: string;
base: string; base: string;
authorizationNeeded: boolean;
secured: boolean;
maxLogs: number; maxLogs: number;
hostname: string; hostname: string;
hosts: { name: string; id: string }[]; hosts: { name: string; id: string }[];

View File

@@ -12,19 +12,6 @@ services:
- 8080:8080 - 8080:8080
build: build:
context: . context: .
auth:
container_name: auth
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOZZLE_FILTER=name=auth
- DOZZLE_USERNAME=foo
- DOZZLE_PASSWORD=bar
- DOZZLE_NO_ANALYTICS=1
ports:
- 9090:8080
build:
context: .
simple-auth: simple-auth:
container_name: simple-auth container_name: simple-auth
volumes: volumes:
@@ -82,5 +69,4 @@ services:
depends_on: depends_on:
- dozzle - dozzle
- custom_base - custom_base
- auth
- remote - remote

View File

@@ -1,9 +0,0 @@
import { test, expect } from "@playwright/test";
test("authentication", async ({ page }) => {
await page.goto("http://auth:8080/");
await page.locator('input[name="username"]').fill("foo");
await page.locator('input[name="password"]').fill("bar");
await page.locator('button[type="submit"]').click();
await expect(page.getByTestId("containers")).toHaveText("Containers");
});

2
go.mod
View File

@@ -10,7 +10,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/gorilla/sessions v1.2.2
github.com/magiconair/properties v1.8.7 github.com/magiconair/properties v1.8.7
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
@@ -42,7 +41,6 @@ require (
github.com/distribution/reference v0.5.0 // indirect github.com/distribution/reference v0.5.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect github.com/google/go-cmp v0.5.5 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/kr/pretty v0.2.1 // indirect github.com/kr/pretty v0.2.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect

6
go.sum
View File

@@ -45,12 +45,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=

View File

@@ -1,106 +1,12 @@
package web package web
import ( import (
"crypto/sha256"
"fmt"
"net/http" "net/http"
"time" "time"
"github.com/gorilla/sessions"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var secured = false
var store *sessions.CookieStore
const authorityKey = "AUTH_TIMESTAMP"
const sessionName = "session"
func initializeAuth(h *handler) {
secured = false
if h.config.Username != "" && h.config.Password != "" {
store = sessions.NewCookieStore(generateSessionStorageKey(h.config.Username, h.config.Password))
store.Options.HttpOnly = true
store.Options.SameSite = http.SameSiteLaxMode
store.Options.MaxAge = 0
secured = true
}
}
func authorizationRequired(next http.Handler) http.Handler {
if secured {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isAuthorized(r) {
next.ServeHTTP(w, r)
} else {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
})
} else {
return next
}
}
func isAuthorized(r *http.Request) bool {
if !secured {
return true
}
session, _ := store.Get(r, sessionName)
if session.IsNew {
return false
}
if _, ok := session.Values[authorityKey]; ok {
// TODO check for timestamp
return true
}
return false
}
func (h *handler) isAuthorizationNeeded(r *http.Request) bool {
return secured && !isAuthorized(r)
}
func (h *handler) validateCredentials(w http.ResponseWriter, r *http.Request) {
if !secured {
log.Panic("Validating credentials without username and password should not happen")
}
if r.Method != "POST" {
log.Fatal("Expecting credential validation method to be POST")
http.Error(w, http.StatusText(http.StatusNotAcceptable), http.StatusNotAcceptable)
return
}
if err := r.ParseMultipartForm(4 * 1024); err != nil {
log.Fatalf("Error while parsing form data: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user := r.PostFormValue("username")
pass := r.PostFormValue("password")
session, _ := store.Get(r, sessionName)
if user == h.config.Username && pass == h.config.Password {
session.Values[authorityKey] = time.Now().Unix()
if err := session.Save(r, w); err != nil {
log.Fatalf("Error while parsing saving session: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(http.StatusText(http.StatusOK)))
return
}
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
func (h *handler) createToken(w http.ResponseWriter, r *http.Request) { func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
user := r.PostFormValue("username") user := r.PostFormValue("username")
pass := r.PostFormValue("password") pass := r.PostFormValue("password")
@@ -134,25 +40,3 @@ func (h *handler) deleteToken(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte(http.StatusText(http.StatusOK))) w.Write([]byte(http.StatusText(http.StatusOK)))
} }
func (h *handler) clearSession(w http.ResponseWriter, r *http.Request) {
if !secured {
log.Panic("Validating credentials with secured=false should not happen")
}
session, _ := store.Get(r, sessionName)
delete(session.Values, authorityKey)
if err := session.Save(r, w); err != nil {
log.Fatalf("Error while parsing saving session: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, h.config.Base, http.StatusTemporaryRedirect)
}
func generateSessionStorageKey(username string, password string) []byte {
key := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", username, password)))
return key[:]
}

View File

@@ -21,10 +21,6 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
if err == nil && req.URL.Path != "" && req.URL.Path != "/" { if err == nil && req.URL.Path != "" && req.URL.Path != "/" {
fileServer.ServeHTTP(w, req) fileServer.ServeHTTP(w, req)
} else { } else {
if !isAuthorized(req) && req.URL.Path != "login" {
http.Redirect(w, req, path.Clean(h.config.Base+"/login"), http.StatusTemporaryRedirect)
return
}
h.executeTemplate(w, req) h.executeTemplate(w, req)
} }
} }
@@ -43,14 +39,12 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
}) })
config := map[string]interface{}{ config := map[string]interface{}{
"base": base, "base": base,
"version": h.config.Version, "version": h.config.Version,
"authorizationNeeded": h.isAuthorizationNeeded(req), "hostname": h.config.Hostname,
"secured": secured, "hosts": hosts,
"hostname": h.config.Hostname, "authProvider": h.config.Authorization.Provider,
"hosts": hosts, "enableActions": h.config.EnableActions,
"authProvider": h.config.Authorization.Provider,
"enableActions": h.config.EnableActions,
} }
user := auth.UserFromContext(req.Context()) user := auth.UserFromContext(req.Context())

View File

@@ -29,8 +29,6 @@ type Config struct {
Base string Base string
Addr string Addr string
Version string Version string
Username string
Password string
Hostname string Hostname string
NoAnalytics bool NoAnalytics bool
Dev bool Dev bool
@@ -79,8 +77,6 @@ func CreateServer(clients map[string]DockerClient, content fs.FS, config Config)
var fileServer http.Handler var fileServer http.Handler
func createRouter(h *handler) *chi.Mux { func createRouter(h *handler) *chi.Mux {
initializeAuth(h)
base := h.config.Base base := h.config.Base
r := chi.NewRouter() r := chi.NewRouter()
@@ -101,7 +97,6 @@ func createRouter(h *handler) *chi.Mux {
if h.config.Authorization.Provider != NONE { if h.config.Authorization.Provider != NONE {
r.Use(auth.RequireAuthentication) r.Use(auth.RequireAuthentication)
} }
r.Use(authorizationRequired) // TODO remove this
r.Get("/api/logs/stream/{host}/{id}", h.streamLogs) r.Get("/api/logs/stream/{host}/{id}", h.streamLogs)
r.Get("/api/logs/download/{host}/{id}", h.downloadLogs) r.Get("/api/logs/download/{host}/{id}", h.downloadLogs)
r.Get("/api/logs/{host}/{id}", h.fetchLogsBetweenDates) r.Get("/api/logs/{host}/{id}", h.fetchLogsBetweenDates)
@@ -112,7 +107,6 @@ func createRouter(h *handler) *chi.Mux {
r.Get("/api/releases", h.releases) r.Get("/api/releases", h.releases)
r.Get("/api/profile/avatar", h.avatar) r.Get("/api/profile/avatar", h.avatar)
r.Patch("/api/profile", h.updateProfile) r.Patch("/api/profile", h.updateProfile)
r.Get("/logout", h.clearSession) // TODO remove this
r.Get("/version", h.version) r.Get("/version", h.version)
}) })
@@ -127,7 +121,6 @@ func createRouter(h *handler) *chi.Mux {
r.Delete("/api/token", h.deleteToken) r.Delete("/api/token", h.deleteToken)
} }
r.Post("/api/validateCredentials", h.validateCredentials) // TODO remove this
r.Get("/healthcheck", h.healthcheck) r.Get("/healthcheck", h.healthcheck)
}) })

View File

@@ -1,23 +1,14 @@
package web package web
import ( import (
"bytes"
"io"
"mime/multipart"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"time"
"github.com/magiconair/properties/assert" "github.com/magiconair/properties/assert"
"github.com/amir20/dozzle/internal/docker"
"github.com/beme/abide" "github.com/beme/abide"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/spf13/afero" "github.com/spf13/afero"
@@ -49,19 +40,6 @@ func Test_createRoutes_redirect(t *testing.T) {
abide.AssertHTTPResponse(t, t.Name(), rr.Result()) abide.AssertHTTPResponse(t, t.Name(), rr.Result())
} }
func Test_createRoutes_redirect_with_auth(t *testing.T) {
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
req, err := http.NewRequest("GET", "/foobar/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_foobar(t *testing.T) { func Test_createRoutes_foobar(t *testing.T) {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("foo page"), 0644), "WriteFile should have no error.") require.NoError(t, afero.WriteFile(fs, "index.html", []byte("foo page"), 0644), "WriteFile should have no error.")
@@ -100,116 +78,3 @@ func Test_createRoutes_version(t *testing.T) {
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result()) abide.AssertHTTPResponse(t, t.Name(), rr.Result())
} }
func Test_createRoutes_username_password(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
req, err := http.NewRequest("GET", "/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_username_password_invalid(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
req, err := http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_username_password_login_happy(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("username")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("amir"))
require.NoError(t, err, "Copying field should not result in error.")
fw, err = writer.CreateFormField("password")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("password"))
require.NoError(t, err, "Copying field should not result in error.")
writer.Close()
req, err := http.NewRequest("POST", "/api/validateCredentials", bytes.NewReader(body.Bytes()))
req.Header.Set("Content-Type", writer.FormDataContentType())
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, 200)
cookie := rr.Header().Get("Set-Cookie")
assert.Matches(t, cookie, "session=.+")
}
func Test_createRoutes_username_password_login_failed(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("username")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("amir"))
require.NoError(t, err, "Copying field should not result in error.")
fw, err = writer.CreateFormField("password")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("bad"))
require.NoError(t, err, "Copying field should not result in error.")
writer.Close()
req, err := http.NewRequest("POST", "/api/validateCredentials", bytes.NewReader(body.Bytes()))
req.Header.Set("Content-Type", writer.FormDataContentType())
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, 401)
}
func Test_createRoutes_username_password_valid_session(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
mockedClient.On("ContainerLogs", mock.Anything, "123", "", docker.STDALL).Return(io.NopCloser(strings.NewReader("test data")), io.EOF)
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
// Get cookie first
req, err := http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
require.NoError(t, err, "NewRequest should not return an error.")
session, _ := store.Get(req, sessionName)
session.Values[authorityKey] = time.Now().Unix()
recorder := httptest.NewRecorder()
session.Save(req, recorder)
cookies := recorder.Result().Cookies()
// Test with cookie
req, err = http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
require.NoError(t, err, "NewRequest should not return an error.")
req.AddCookie(cookies[0])
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_username_password_invalid_session(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
mockedClient.On("ContainerLogs", mock.Anything, "since", docker.STDALL).Return(io.NopCloser(strings.NewReader("test data")), io.EOF)
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
req, err := http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
require.NoError(t, err, "NewRequest should not return an error.")
req.AddCookie(&http.Cookie{Name: "session", Value: "baddata"})
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, 401)
}

17
main.go
View File

@@ -44,8 +44,6 @@ type args struct {
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."` Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
Username string `arg:"env:DOZZLE_USERNAME" help:"sets the username for auth."` Username string `arg:"env:DOZZLE_USERNAME" help:"sets the username for auth."`
Password string `arg:"env:DOZZLE_PASSWORD" help:"sets password for auth"` Password string `arg:"env:DOZZLE_PASSWORD" help:"sets password for auth"`
UsernameFile *DockerSecret `arg:"env:DOZZLE_USERNAME_FILE" help:"sets the secret path read username for auth."`
PasswordFile *DockerSecret `arg:"env:DOZZLE_PASSWORD_FILE" help:"sets the secret path read password for auth"`
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"` NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
WaitForDockerSeconds int `arg:"--wait-for-docker-seconds,env:DOZZLE_WAIT_FOR_DOCKER_SECONDS" help:"wait for docker to be available for at most this many seconds before starting the server."` WaitForDockerSeconds int `arg:"--wait-for-docker-seconds,env:DOZZLE_WAIT_FOR_DOCKER_SECONDS" help:"wait for docker to be available for at most this many seconds before starting the server."`
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."` FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
@@ -193,8 +191,6 @@ func createServer(args args, clients map[string]web.DockerClient) *http.Server {
Addr: args.Addr, Addr: args.Addr,
Base: args.Base, Base: args.Base,
Version: version, Version: version,
Username: args.Username,
Password: args.Password,
Hostname: args.Hostname, Hostname: args.Hostname,
NoAnalytics: args.NoAnalytics, NoAnalytics: args.NoAnalytics,
Dev: dev, Dev: dev,
@@ -274,19 +270,8 @@ func parseArgs() args {
args.Filter[key] = append(args.Filter[key], val) args.Filter[key] = append(args.Filter[key], val)
} }
if args.Username == "" && args.UsernameFile != nil {
args.Username = args.UsernameFile.Value
}
if args.Password == "" && args.PasswordFile != nil {
args.Password = args.PasswordFile.Value
}
if args.Username != "" || args.Password != "" { if args.Username != "" || args.Password != "" {
log.Warn("Using --username and --password is being deprecated and removed in v6.x. Use --auth-provider instead. See https://dozzle.dev/guide/authentication#file-based-user-management for more information.") log.Fatal("Using --username and --password is removed v6.x. See https://github.com/amir20/dozzle/issues/2630 for details.")
if args.Username == "" || args.Password == "" {
log.Fatalf("Username AND password are required for authentication")
}
} }
return args return args
} }